diff --git a/docs/dictionary.txt b/docs/dictionary.txt index 268cde7a..94bfdb49 100644 --- a/docs/dictionary.txt +++ b/docs/dictionary.txt @@ -28,3 +28,4 @@ Allpainted : allPainted 치수선: dimensionLine 복도치수: planeSize 실제치수: actualSize +모듈설치면: moduleSetupSurface \ No newline at end of file diff --git a/package.json b/package.json index f338fbe4..c92c5d80 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "react-icons": "^5.3.0", "react-loading-skeleton": "^3.5.0", "react-responsive-modal": "^6.4.2", + "react-spinners": "^0.14.1", "recoil": "^0.7.7", "sweetalert2": "^11.14.1", "sweetalert2-react-content": "^5.0.7", diff --git a/public/static/images/canvas/side_icon10.svg b/public/static/images/canvas/side_icon10.svg new file mode 100644 index 00000000..d7eba9ec --- /dev/null +++ b/public/static/images/canvas/side_icon10.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/app/QcastProvider.js b/src/app/QcastProvider.js index b88dd63c..23ad5a75 100644 --- a/src/app/QcastProvider.js +++ b/src/app/QcastProvider.js @@ -1,23 +1,40 @@ 'use client' -import { useEffect, useState } from 'react' +import { createContext, useEffect, useState } from 'react' import { ErrorBoundary } from 'next/dist/client/components/error-boundary' import { useCommonCode } from '@/hooks/common/useCommonCode' import { usePlan } from '@/hooks/usePlan' import ServerError from './error' import '@/styles/common.scss' +import GlobalSpinner from '@/components/common/spinner/GlobalSpinner' + +export const QcastContext = createContext({ + qcastState: {}, + setQcastState: () => {}, + isGlobalLoading: false, + setIsGlobalLoading: () => {}, +}) export const QcastProvider = ({ children }) => { const [planSave, setPlanSave] = useState(false) + const [isGlobalLoading, setIsGlobalLoading] = useState(false) const { currentCanvasPlan, modifiedPlans, checkUnsavedCanvasPlan } = usePlan() const { commonCode, findCommonCode } = useCommonCode() + const [qcastState, setQcastState] = useState({ + saleStoreId: '', + saleStoreName: '', + objectList: [], + businessCharger: null, + businessChargerMail: null, + }) + useEffect(() => { const targetElement = document.getElementById('canvas') if (!targetElement && currentCanvasPlan?.id && planSave) { - setPlanSave((prev) => !prev) - checkUnsavedCanvasPlan(currentCanvasPlan.userId) + setPlanSave((prev) => !prev) + checkUnsavedCanvasPlan(currentCanvasPlan.userId) } else if (targetElement && currentCanvasPlan?.id) { setPlanSave(true) } @@ -30,7 +47,14 @@ export const QcastProvider = ({ children }) => { return ( <> - }>{children} + {isGlobalLoading && ( +
+ +
+ )} + + }>{children} + ) } diff --git a/src/app/api/html2canvas/route.js b/src/app/api/html2canvas/route.js index 02731f07..ada6c54a 100644 --- a/src/app/api/html2canvas/route.js +++ b/src/app/api/html2canvas/route.js @@ -5,7 +5,7 @@ import fs from 'fs/promises' import { NextResponse } from 'next/server' export async function GET(req) { - const path = 'public/mapImages' + const path = 'public/plan-map-images' const q = req.nextUrl.searchParams.get('q') const fileNm = req.nextUrl.searchParams.get('fileNm') const zoom = req.nextUrl.searchParams.get('zoom') diff --git a/src/app/api/image-upload/route.js b/src/app/api/image-upload/route.js new file mode 100644 index 00000000..e817bd3b --- /dev/null +++ b/src/app/api/image-upload/route.js @@ -0,0 +1,25 @@ +'use server' + +import fs from 'fs/promises' + +import { NextResponse } from 'next/server' + +export async function POST(req) { + const path = 'public/plan-bg-images' + + const formData = await req.formData() + const file = formData.get('file') + const arrayBuffer = await file.arrayBuffer() + const buffer = Buffer.from(arrayBuffer) + // const buffer = new Uint8Array(arrayBuffer) + + try { + await fs.readdir(path) + } catch { + await fs.mkdir(path) + } finally { + await fs.writeFile(`${path}/${file.name}`, buffer) + } + + return NextResponse.json({ fileNm: `${file.name}` }) +} diff --git a/src/app/floor-plan/EventProvider.js b/src/app/floor-plan/EventProvider.js new file mode 100644 index 00000000..991b19bb --- /dev/null +++ b/src/app/floor-plan/EventProvider.js @@ -0,0 +1,34 @@ +import { useEvent } from '@/hooks/useEvent' +import { createContext, useState } from 'react' + +export const EventContext = createContext({}) + +const EventProvider = ({ children }) => { + const { + addDocumentEventListener, + addCanvasMouseEventListener, + addTargetMouseEventListener, + removeAllMouseEventListeners, + removeAllDocumentEventListeners, + removeDocumentEvent, + removeMouseEvent, + removeMouseLine, + initEvent, + } = useEvent() + + const [value, setValue] = useState({ + addDocumentEventListener, + addCanvasMouseEventListener, + addTargetMouseEventListener, + removeAllMouseEventListeners, + removeAllDocumentEventListeners, + removeDocumentEvent, + removeMouseEvent, + removeMouseLine, + initEvent, + }) + + return {children} +} + +export default EventProvider diff --git a/src/app/floor-plan/FloorPlanProvider.js b/src/app/floor-plan/FloorPlanProvider.js index cb23116b..59a05edb 100644 --- a/src/app/floor-plan/FloorPlanProvider.js +++ b/src/app/floor-plan/FloorPlanProvider.js @@ -1,9 +1,73 @@ 'ues client' -import { ErrorBoundary } from 'next/dist/client/components/error-boundary' -import ServerError from '../error' +import { correntObjectNoState } from '@/store/settingAtom' +import { notFound, usePathname, useSearchParams } from 'next/navigation' +// import { ErrorBoundary } from 'next/dist/client/components/error-boundary' +// import ServerError from '../error' +import { createContext, useEffect, useReducer, useState } from 'react' +import { useSetRecoilState } from 'recoil' -export const FloorPlanProvider = ({ children }) => { - console.log('FloorPlanProvider') - return }>{children} +const reducer = (prevState, nextState) => { + return { ...prevState, ...nextState } } + +const defaultEstimateData = { + estimateDate: new Date(), //견적일 + charger: '', //담당자 + objectName: '', //안건명 + objectNameOmit: '', //경칭코드 + estimateType: '', //주문분류 + remarks: '', //비고 + estimateOption: '', //견적특이사항 + itemList: [], + fileList: [], + fileFlg: '0', //후일 자료 제출 (체크 1 노체크 0) + priceCd: '', +} + +export const FloorPlanContext = createContext({ + floorPlanState: {}, + setFloorPlanState: () => {}, + estimateContextState: {}, + setEstimateContextState: () => {}, +}) + +const FloorPlanProvider = ({ children }) => { + const pathname = usePathname() + const setCurrentObjectNo = useSetRecoilState(correntObjectNoState) + const searchParams = useSearchParams() + const objectNo = searchParams.get('objectNo') + const pid = searchParams.get('pid') + + if (pathname === '/floor-plan') { + if (pid === undefined || pid === '' || objectNo === undefined || objectNo === '') { + notFound() + } + setCurrentObjectNo(objectNo) + } + + const [floorPlanState, setFloorPlanState] = useState({ + // 플랜 파일 업로드 모달 오픈 제어 + refFileModalOpen: false, + // 플랜 회전 모드 제어 + toggleRotate: false, + // 물건 번호 + objectNo, + // 플랜 번호 + pid, + }) + + useEffect(() => { + console.log('🚀 ~ FloorPlanProvider ~ floorPlanState:', floorPlanState) + }, [floorPlanState]) + + const [estimateContextState, setEstimateContextState] = useReducer(reducer, defaultEstimateData) + + return ( + + {children} + + ) +} + +export default FloorPlanProvider diff --git a/src/app/floor-plan/layout.js b/src/app/floor-plan/layout.js index 782bc51e..4bb1e355 100644 --- a/src/app/floor-plan/layout.js +++ b/src/app/floor-plan/layout.js @@ -1,17 +1,22 @@ 'use client' + +import FloorPlanProvider from './FloorPlanProvider' import FloorPlan from '@/components/floor-plan/FloorPlan' -import { FloorPlanProvider } from './FloorPlanProvider' import CanvasLayout from '@/components/floor-plan/CanvasLayout' +import EventProvider from './EventProvider' export default function FloorPlanLayout({ children }) { - console.log('FloorPlanLayout') + console.log('🚀 ~ FloorPlanLayout ~ FloorPlanLayout:') + return ( <> - - - {children} - - + {/**/} + + + {children} + + + {/**/} ) } diff --git a/src/app/layout.js b/src/app/layout.js index 684dfb7f..fbe6c39a 100644 --- a/src/app/layout.js +++ b/src/app/layout.js @@ -62,16 +62,16 @@ export default async function RootLayout({ children }) { {headerPathname === '/login' || headerPathname === '/join' ? ( {children} ) : ( -
-
-
- - + +
+
+
+ {children} - +
+
-
-
+ )} diff --git a/src/app/management/ManagementProvider.js b/src/app/management/ManagementProvider.js new file mode 100644 index 00000000..197b30c0 --- /dev/null +++ b/src/app/management/ManagementProvider.js @@ -0,0 +1,20 @@ +'ues client' + +import { createContext, useEffect, useState } from 'react' + +export const ManagementContext = createContext({ + managementState: {}, + setManagementState: () => {}, +}) + +const ManagementProvider = ({ children }) => { + const [managementState, setManagementState] = useState({}) + + useEffect(() => { + console.log('🚀 ~ managementState:', managementState) + }, [managementState]) + + return {children} +} + +export default ManagementProvider diff --git a/src/app/management/layout.js b/src/app/management/layout.js new file mode 100644 index 00000000..b0e031c7 --- /dev/null +++ b/src/app/management/layout.js @@ -0,0 +1,7 @@ +'use client' + +import ManagementProvider from './ManagementProvider' + +export default function ManagementLayout({ children }) { + return {children} +} diff --git a/src/common/common.js b/src/common/common.js index eb86bb89..02a953fe 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -61,6 +61,8 @@ export const LINE_TYPE = { DEFAULT: 'default', EAVES: 'eaves', GABLE: 'gable', + GABLE_LEFT: 'gableLeft', //케라바 왼쪽 + GABLE_RIGHT: 'gableRight', //케라바 오른쪽 WALL: 'wall', HIPANDGABLE: 'hipAndGable', JERKINHEAD: 'jerkinhead', @@ -74,30 +76,20 @@ export const LINE_TYPE = { HIP: 'hip', RIDGE: 'ridge', GABLE: 'gable', - VALLEY: 'valley', VERGE: 'verge', + ONESIDE_FLOW_RIDGE: 'onesideFlowRidge', //한쪽흐름 용마루 + YOSEMUNE: 'yosemune', //요세무네 + VALLEY: 'valley', //골짜기 + L_ABANDON_VALLEY: 'lAbandonValley', //l의버림계곡 + MANSARD: 'mansard', //맨사드 + WALL_COLLECTION: 'wallCollection', //벽취합 + WALL_COLLECTION_TYPE: 'wallCollectionType', //벽취합(형) + WALL_COLLECTION_FLOW: 'wallCollectionFlow', //벽취합(흐름) + WALL_COLLECTION_FLOW_LEFT: 'wallCollectionFlowLeft', //벽취합(흐름 왼쪽) + WALL_COLLECTION_FLOW_RIGHT: 'wallCollectionFlowRight', //벽취합(흐름 오른쪽) }, } -export const LineType = { - EAVES: 'eaves', // 처마 - RIDGE: 'ridge', // 용마루.... - YOSEMUNE: 'yosemune', //요세무네 - ONESIDE_FLOW_RIDGE: 'onesideFlowRidge', //한쪽흐름 용마루 - WALL_COLLECTION: 'wallCollection', //벽취합 - WALL_COLLECTION_TYPE: 'wallCollectionType', //벽취합(형) - WALL_COLLECTION_FLOW: 'wallCollectionFlow', //벽취합(흐름) - WALL_COLLECTION_FLOW_LEFT: 'wallCollectionFlowLeft', //벽취합(흐름 왼쪽) - WALL_COLLECTION_FLOW_RIGHT: 'wallCollectionFlowRight', //벽취합(흐름 오른쪽) - KERABA: 'keraba', //케라바 - KERABA_LEFT: 'kerabaLeft', //케라바 왼쪽 - KERABA_RIGHT: 'kerabaRight', //케라바 오른쪽 - VALLEY: 'valley', //골짜기 - L_ABANDON_VALLEY: 'lAbandonValley', //l의버림계곡 - MANSARD: 'mansard', //맨사드 - NO_SETTING: 'noSetting', //설정없음 -} - // 오브젝트 배치 > 개구배치, 그림자배치 export const BATCH_TYPE = { OPENING: 'opening', @@ -119,6 +111,7 @@ export const POLYGON_TYPE = { ROOF: 'roof', WALL: 'wall', TRESTLE: 'trestle', + MODULE_SETUP_SURFACE: 'moduleSetupSurface', } export const SAVE_KEY = [ @@ -162,6 +155,12 @@ export const SAVE_KEY = [ 'planeSize', 'actualSize', 'surfaceId', + 'lines', + 'offset', + 'arrow', + 'surfaceCompass', + 'moduleCompass', + 'isFixed', ] export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype] diff --git a/src/components/Main.jsx b/src/components/Main.jsx index 11046eed..e3c491e3 100644 --- a/src/components/Main.jsx +++ b/src/components/Main.jsx @@ -1,9 +1,8 @@ 'use client' -import React, { useEffect, useState } from 'react' +import { useEffect, useState, useContext } from 'react' import { useRouter } from 'next/navigation' import { useRecoilState, useRecoilValue } from 'recoil' -import { sessionStore } from '@/store/commonAtom' import { useAxios } from '@/hooks/useAxios' import { globalLocaleStore } from '@/store/localeAtom' import MainContents from './main/MainContents' @@ -12,8 +11,11 @@ import { stuffSearchState } from '@/store/stuffAtom' import '@/styles/contents.scss' import ChangePasswordPop from './main/ChangePasswordPop' import { searchState } from '@/store/boardAtom' +import { SessionContext } from '@/app/SessionProvider' +import { QcastContext } from '@/app/QcastProvider' + export default function MainPage() { - const sessionState = useRecoilValue(sessionStore) + const { session } = useContext(SessionContext) const globalLocaleState = useRecoilValue(globalLocaleStore) @@ -25,22 +27,24 @@ export default function MainPage() { const [searchRadioType, setSearchRadioType] = useState('object') - const [saleStoreId, setSaleStoreId] = useState('') - const [saleStoreName, setSaleStoreName] = useState('') + // const [saleStoreId, setSaleStoreId] = useState('') + // const [saleStoreName, setSaleStoreName] = useState('') const [stuffSearch, setStuffSearch] = useRecoilState(stuffSearchState) const [searchForm, setSearchForm] = useRecoilState(searchState) - useEffect(() => { - if (sessionState.pwdInitYn === 'Y') { - fetchObjectList() - } - }, [sessionState]) + const { qcastState } = useContext(QcastContext) + + // useEffect(() => { + // if (session.pwdInitYn === 'Y') { + // fetchObjectList() + // } + // }, [session]) const fetchObjectList = async () => { try { - const apiUrl = `/api/main-page/object/${sessionState?.storeId}/list` + const apiUrl = `/api/main-page/object/${session?.storeId}/list` await promiseGet({ url: apiUrl, }).then((res) => { @@ -95,7 +99,7 @@ export default function MainPage() { return ( <> - {(sessionState?.pwdInitYn !== 'N' && ( + {(session?.pwdInitYn !== 'N' && ( <>
@@ -107,7 +111,7 @@ export default function MainPage() {
- {saleStoreId} / {saleStoreName} + {qcastState?.saleStoreId} / {qcastState?.saleStoreName}
diff --git a/src/components/Roof2.jsx b/src/components/Roof2.jsx index 021357c4..731fe8c6 100644 --- a/src/components/Roof2.jsx +++ b/src/components/Roof2.jsx @@ -5,7 +5,7 @@ import { useEffect, useRef, useState } from 'react' import { v4 as uuidv4 } from 'uuid' import { useMode } from '@/hooks/useMode' import { LINE_TYPE, Mode } from '@/common/common' -import { Button, Input } from '@nextui-org/react' +import { Button } from '@nextui-org/react' import RangeSlider from './ui/RangeSlider' import { useRecoilState, useRecoilValue } from 'recoil' import { @@ -410,10 +410,10 @@ export default function Roof2(props) { ] const rectangleType1 = [ - { x: 100, y: 100 }, - { x: 100, y: 600 }, - { x: 300, y: 600 }, - { x: 300, y: 100 }, + { x: 500, y: 100 }, + { x: 500, y: 800 }, + { x: 900, y: 800 }, + { x: 900, y: 100 }, ] const rectangleType2 = [ @@ -445,11 +445,31 @@ export default function Roof2(props) { { x: 113, y: 371.9 }, { x: 762, y: 371.9 }, { x: 762, y: 818.7 }, - { x: 1468.6, y: 818.7 }, - { x: 1468.6, y: 114.9 }, + { x: 1478.6, y: 818.7 }, + { x: 1478.6, y: 114.9 }, ] - const polygon = new QPolygon(type2, { + const test3 = [ + { x: 100, y: 100 }, + { x: 100, y: 600 }, + { x: 600, y: 600 }, + { x: 600, y: 100 }, + { x: 500, y: 100 }, + { x: 500, y: 200 }, + { x: 200, y: 200 }, + { x: 200, y: 100 }, + ] + + const test4 = [ + { x: 100, y: 100 }, + { x: 100, y: 1000 }, + { x: 1100, y: 1000 }, + { x: 1100, y: 550 }, + { x: 500, y: 550 }, + { x: 500, y: 100 }, + ] + + const polygon = new QPolygon(test4, { fill: 'transparent', stroke: 'green', strokeWidth: 1, @@ -1010,7 +1030,7 @@ export default function Roof2(props) { )} -
+ {/*

각도 입력(0~360) 후 방향설정 클릭

방향 설정 -
-
- {/* Compass Circle */} +
*/} + {/*
+ Compass Circle
- {/* N, S, E, W Labels */} + N, S, E, W Labels
N
S
@@ -1046,9 +1066,9 @@ export default function Roof2(props) {
- {/* Compass Pointer */} + Compass Pointer
- {/* Red Upper Triangle */} + Red Upper Triangle
-
+
*/}
{!canvas ? null : mode === Mode.DRAW_LINE ? ( diff --git a/src/components/auth/Login.jsx b/src/components/auth/Login.jsx index 283d7d99..af50a515 100644 --- a/src/components/auth/Login.jsx +++ b/src/components/auth/Login.jsx @@ -80,79 +80,32 @@ export default function Login() { e.preventDefault() const formData = new FormData(e.target) - /////////////////////////////////////////////////////////// - // 임시 로그인 처리 - setSession({ - userId: 'NEW016610', - saleStoreId: null, - name: null, - mail: null, - tel: null, - storeId: 'TEMP02', - userNm: 'ㅇㅇ6610', - userNmKana: '신규사용자 16610', - category: '인상6610', - telNo: '336610', - fax: null, - email: 't10t@naver.com', - pwdInitYn: 'Y', - storeLvl: '1', - groupId: '60000', - custCd: '100000', - }) - setSessionState({ - userId: 'NEW016610', - saleStoreId: null, - name: null, - mail: null, - tel: null, - storeId: 'TEMP02', - userNm: 'ㅇㅇ6610', - userNmKana: '신규사용자 16610', - category: '인상6610', - telNo: '336610', - fax: null, - email: 't10t@naver.com', - pwdInitYn: 'Y', - storeLvl: '1', - groupId: '60000', - custCd: '100000', - }) - if (chkLoginId) { - Cookies.set('chkLoginId', formData.get('id'), { expires: 7 }) - } else { - Cookies.remove('chkLoginId') + // 로그인 처리 시작 + const param = { + loginId: formData.get('id'), + pwd: formData.get('password'), } - router.push('/') - // 임시 로그인 처리 끝 - /////////////////////////////////////////////////////////// - - // 로그인 처리 시작 - ** 상단 임시 로그인 추후 삭제 필요 ** - // const param = { - // loginId: formData.get('id'), - // pwd: formData.get('password'), - // } - // await promisePost({ url: '/api/login/v1.0/login', data: param }) - // .then((res) => { - // if (res) { - // if (res.data.result.resultCode === 'S') { - // setSession(res.data.data) - // setSessionState(res.data.data) - // // ID SAVE 체크되어 있는 경우, 쿠키 저장 - // if (chkLoginId) { - // Cookies.set('chkLoginId', formData.get('id'), { expires: 7 }) - // } else { - // Cookies.remove('chkLoginId') - // } - // router.push('/') - // } else { - // alert(res.data.result.resultMsg) - // } - // } - // }) - // .catch((error) => { - // alert(error.response.data.message) - // }) + await promisePost({ url: '/api/login/v1.0/login', data: param }) + .then((res) => { + if (res) { + if (res.data.result.resultCode === 'S') { + setSession(res.data.data) + setSessionState(res.data.data) + // ID SAVE 체크되어 있는 경우, 쿠키 저장 + if (chkLoginId) { + Cookies.set('chkLoginId', formData.get('id'), { expires: 7 }) + } else { + Cookies.remove('chkLoginId') + } + router.push('/') + } else { + alert(res.data.result.resultMsg) + } + } + }) + .catch((error) => { + alert(error.response.data.message) + }) } // 비밀번호 초기화 관련 diff --git a/src/components/common/context-menu/QContextMenu.jsx b/src/components/common/context-menu/QContextMenu.jsx index f6665930..19791ffc 100644 --- a/src/components/common/context-menu/QContextMenu.jsx +++ b/src/components/common/context-menu/QContextMenu.jsx @@ -15,6 +15,7 @@ export default function QContextMenu(props) { const { tempGridMode, setTempGridMode } = useTempGrid() const { handleKeyup } = useContextMenu() const { addDocumentEventListener, removeDocumentEvent } = useEvent() + // const { addDocumentEventListener, removeDocumentEvent } = useContext(EventContext) let contextType = '' diff --git a/src/components/common/font/FontSetting.jsx b/src/components/common/font/FontSetting.jsx index a658871d..15f1146a 100644 --- a/src/components/common/font/FontSetting.jsx +++ b/src/components/common/font/FontSetting.jsx @@ -1,78 +1,69 @@ import WithDraggable from '@/components/common/draggable/WithDraggable' import QSelectBox from '@/components/common/select/QSelectBox' import { usePopup } from '@/hooks/usePopup' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { useMessage } from '@/hooks/useMessage' -import { useRecoilState, useRecoilValue } from 'recoil' -import { fontSelector, globalFontAtom } from '@/store/fontAtom' +import { useRecoilValue } from 'recoil' import { contextPopupPositionState } from '@/store/popupAtom' +import { useCanvasSetting } from '@/hooks/option/useCanvasSetting' const fonts = [ - { name: 'MS PGothic', value: 'MS PGothic' }, - { name: '@Yu Gothic', value: '@Yu Gothic' }, - { name: 'Yu Gothic', value: 'Yu Gothic' }, - { name: '@Yu Gothic UI', value: '@Yu Gothic UI' }, - { name: 'Yu Gothic UI', value: 'Yu Gothic UI' }, + { id: 1, name: 'MS PGothic', value: 'MS PGothic' }, + { id: 2, name: '@Yu Gothic', value: '@Yu Gothic' }, + { id: 3, name: 'Yu Gothic', value: 'Yu Gothic' }, + { id: 4, name: '@Yu Gothic UI', value: '@Yu Gothic UI' }, + { id: 5, name: 'Yu Gothic UI', value: 'Yu Gothic UI' }, ] const fontSizes = [ ...Array.from({ length: 4 }).map((_, index) => { - return { name: index + 8, value: index + 8 } + return { id: index + 8, name: index + 8, value: index + 8 } }), ...Array.from({ length: 9 }).map((_, index) => { - return { name: (index + 6) * 2, value: (index + 6) * 2 } + return { id: (index + 6) * 2, name: (index + 6) * 2, value: (index + 6) * 2 } }), - { name: 36, value: 36 }, - { name: 48, value: 48 }, - { name: 72, value: 72 }, + { id: 36, name: 36, value: 36 }, + { id: 48, name: 48, value: 48 }, + { id: 72, name: 72, value: 72 }, ] export default function FontSetting(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) - const { id, setIsShow, pos = contextPopupPosition, type, isConfig = false } = props + const { id, setIsShow, pos = contextPopupPosition, type, isConfig = false, onSave, font } = props const { getMessage } = useMessage() const { closePopup } = usePopup() - const [globalFont, setGlobalFont] = useRecoilState(globalFontAtom) - const currentFont = useRecoilValue(fontSelector(type)) + const [selectedFont, setSelectedFont] = useState(font.fontFamily) + const [selectedFontWeight, setSelectedFontWeight] = useState(font.fontWeight) + const [selectedFontSize, setSelectedFontSize] = useState(font.fontSize) + const [selectedFontColor, setSelectedFontColor] = useState(font.fontColor) - const [selectedFont, setSelectedFont] = useState(currentFont.fontFamily) - const [selectedFontWeight, setSelectedFontWeight] = useState(currentFont.fontWeight) - const [selectedFontSize, setSelectedFontSize] = useState(currentFont.fontSize) - const [selectedFontColor, setSelectedFontColor] = useState(currentFont.fontColor) const fontOptions = [ - { name: getMessage('font.style.normal'), value: 'normal' }, - { name: getMessage('font.style.italic'), value: 'italic' }, - { - name: getMessage('font.style.bold'), - value: 'bold', - }, - { name: getMessage('font.style.bold.italic'), value: 'boldAndItalic' }, + { id: 'normal', name: getMessage('font.style.normal'), value: 'normal' }, + { id: 'italic', name: getMessage('font.style.italic'), value: 'italic' }, + { id: 'bold', name: getMessage('font.style.bold'), value: 'bold' }, + { id: 'boldAndItalic', name: getMessage('font.style.bold.italic'), value: 'boldAndItalic' }, ] const fontColors = [ - { name: getMessage('color.black'), value: 'black' }, - { name: getMessage('color.red'), value: 'red' }, - { name: getMessage('color.blue'), value: 'blue' }, - { name: getMessage('color.gray'), value: 'gray' }, - { name: getMessage('color.yellow'), value: 'yellow' }, - { name: getMessage('color.green'), value: 'green' }, - { name: getMessage('color.pink'), value: 'pink' }, - { name: getMessage('color.gold'), value: 'gold' }, - { name: getMessage('color.darkblue'), value: 'darkblue' }, + { id: 'black', name: getMessage('color.black'), value: 'black' }, + { id: 'red', name: getMessage('color.red'), value: 'red' }, + { id: 'blue', name: getMessage('color.blue'), value: 'blue' }, + { id: 'gray', name: getMessage('color.gray'), value: 'gray' }, + { id: 'yellow', name: getMessage('color.yellow'), value: 'yellow' }, + { id: 'green', name: getMessage('color.green'), value: 'green' }, + { id: 'pink', name: getMessage('color.pink'), value: 'pink' }, + { id: 'gold', name: getMessage('color.gold'), value: 'gold' }, + { id: 'darkblue', name: getMessage('color.darkblue'), value: 'darkblue' }, ] + const handleSaveBtn = () => { - setGlobalFont((prev) => { - return { - ...prev, - [type]: { - fontFamily: selectedFont, - fontSize: selectedFontSize, - fontColor: selectedFontColor, - fontWeight: selectedFontWeight, - }, - } + onSave({ + fontFamily: selectedFont, + fontWeight: selectedFontWeight, + fontSize: selectedFontSize, + fontColor: selectedFontColor, }) if (setIsShow) setIsShow(false) - closePopup(id) + closePopup(id, isConfig) } return ( @@ -123,9 +114,10 @@ export default function FontSetting(props) {
diff --git a/src/components/common/select/QSelectBox.jsx b/src/components/common/select/QSelectBox.jsx index df745d64..3da9f30b 100644 --- a/src/components/common/select/QSelectBox.jsx +++ b/src/components/common/select/QSelectBox.jsx @@ -18,8 +18,8 @@ export default function QSelectBox({ title = '', options, onChange, value, disab
{} : () => setOpenSelect(!openSelect)}>

{selected}

    - {options?.map((option) => ( -
  • handleClickSelectOption(option)}> + {options?.map((option, index) => ( +
  • handleClickSelectOption(option)}>
  • ))} diff --git a/src/components/common/spinner/GlobalSpinner.jsx b/src/components/common/spinner/GlobalSpinner.jsx new file mode 100644 index 00000000..c643cc30 --- /dev/null +++ b/src/components/common/spinner/GlobalSpinner.jsx @@ -0,0 +1,7 @@ +'use client' + +import { HashLoader } from 'react-spinners' + +export default function GlobalSpinner() { + return +} diff --git a/src/components/community/modal/BoardDetailModal.jsx b/src/components/community/modal/BoardDetailModal.jsx index c1ba4213..aafb0d8f 100644 --- a/src/components/community/modal/BoardDetailModal.jsx +++ b/src/components/community/modal/BoardDetailModal.jsx @@ -69,7 +69,12 @@ export default function BoardDetailModal({ noticeNo, setOpen }) { )} -
    {boardDetail.contents}
    +
    ') : '', + }} + >
diff --git a/src/components/estimate/Estimate.jsx b/src/components/estimate/Estimate.jsx index 8c6af884..aad02cc1 100644 --- a/src/components/estimate/Estimate.jsx +++ b/src/components/estimate/Estimate.jsx @@ -15,10 +15,15 @@ import { useCommonCode } from '@/hooks/common/useCommonCode' import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController' import { SessionContext } from '@/app/SessionProvider' import Select, { components } from 'react-select' -import { convertNumberToPriceDecimal } from '@/util/common-utils' +import { convertNumberToPriceDecimal, convertNumberToPriceDecimalToFixed } from '@/util/common-utils' import ProductFeaturesPop from './popup/ProductFeaturesPop' +import { v4 as uuidv4 } from 'uuid' export default function Estimate({ params }) { + const [handlePricingFlag, setHandlePricingFlag] = useState(false) + const [specialNoteFirstFlg, setSpecialNoteFirstFlg] = useState(false) + const fixedKey = 'itemKey' + const [itemChangeYn, setItemChangeYn] = useState(false) const { session } = useContext(SessionContext) const [objectNo, setObjectNo] = useState('') //물건번호 const [planNo, setPlanNo] = useState('') //플랜번호 @@ -26,11 +31,14 @@ export default function Estimate({ params }) { const [files, setFiles] = useState([]) // 보내는 첨부파일 const [originFiles, setOriginFiles] = useState([]) //기존 첨부파일 + const [showPriceCd, setShowPriceCd] = useState('') + const [showContentCode, setShowContentCode] = useState('ATTR001') const [productFeaturesPopupOpen, setProductFeaturesPopupOpen] = useState(false) //견적특이사항 팝업 const [showProductFeatureData, setShowProductFeatureData] = useState([]) //팝업에 보여줄 견적특이사항 데이터 + const [selection, setSelection] = useState(new Set()) //견적특이사항 접고 펼치기 const [hidden, setHidden] = useState(false) @@ -43,8 +51,6 @@ export default function Estimate({ params }) { const [storePriceList, setStorePriceList] = useState([]) //가격표시 option - const [tempPriceCd, setTempPriceCd] = useState('') - const [startDate, setStartDate] = useState(new Date()) const singleDatePickerProps = { startDate, @@ -54,12 +60,11 @@ export default function Estimate({ params }) { const objectRecoil = useRecoilValue(floorPlanObjectState) //견적서 상세데이터 - const { state, setState } = useEstimateController(params.pid) - - const [itemList, setItemList] = useState([]) //기존 아이템 리스트 + const { estimateContextState, setEstimateContextState, addItem, handleEstimateFileDownload } = useEstimateController(params.pid) //견적특이사항 List const [specialNoteList, setSpecialNoteList] = useState([]) + const [popShowSpecialNoteList, setPopShowSpecialNoteList] = useState([]) const globalLocaleState = useRecoilValue(globalLocaleStore) const { get, promisePost } = useAxios(globalLocaleState) @@ -95,40 +100,83 @@ export default function Estimate({ params }) { setDisplayItemList(res) } }) + //견적특이사항 API호출 + //여러개 선택하면 구분자로 (、) + //제품영역 팝업용 + let url = '/api/estimate/special-note-list' + get({ url: url }).then((res) => { + if (isNotEmptyArray(res)) { + setPopShowSpecialNoteList(res) + } + }) }, []) useEffect(() => { //견적특이사항 API호출 //여러개 선택하면 구분자로 (、) - let url = `/api/estimate/special-note-list` - get({ url: url }).then((res) => { - if (isNotEmptyArray(res)) { - if (state?.estimateOption) { - res.map((row) => { - let estimateOption = state?.estimateOption?.split('、') - row.text = false - estimateOption.map((row2) => { - if (row2 === row.code) { - row.text = true + //체크용 + if (!specialNoteFirstFlg) { + let url = `/api/estimate/special-note-title-list` + get({ url: url }).then((res) => { + if (isNotEmptyArray(res)) { + //디테일 ATTR001、ATTR002、ATTR003、ATTR007、ATTR009、ATTR010、ATTR015、ATTR019 + if (estimateContextState?.estimateOption) { + res.map((row) => { + let estimateOption = estimateContextState?.estimateOption?.split('、') + // console.log('최초:::', estimateOption) + row.check = false + estimateOption.map((row2) => { + if (row.pkgYn === '0') { + if (row2 === row.code) { + row.check = true + } + } else { + if (row.code.includes(row2)) { + row.check = true + return + } + } + }) + //detail과 상관없이 디폴트 체크목록 + //ATTR003,ATTR007 + if (row.code === 'ATTR003') { + row.check = true + } + if (row.code === 'ATTR007') { + row.check = true } }) - }) - setSpecialNoteList(res) + + setSpecialNoteList(res) + + setSpecialNoteFirstFlg(true) + } } - } - }) - }, [state?.estimateOption]) + }) + } + }, [estimateContextState?.estimateOption]) + + //API데이터로 견적일 셋팅 + let begin = 1 + useEffect(() => { + if (begin === 1) { + setStartDate(estimateContextState?.estimateDate) + begin++ + } + }, [estimateContextState?.estimateDate]) //견적일 set useEffect(() => { let estimateDate = dayjs(startDate).format('YYYY-MM-DD') - setState({ estimateDate: estimateDate }) + if (begin === 1) { + setEstimateContextState({ estimateDate: estimateDate }) + } }, [startDate]) useEffect(() => { - //선택된 견적특이사항 setState + //선택된 견적특이사항 setEstimateContextState if (isNotEmptyArray(specialNoteList)) { - const liveCheckedData = specialNoteList.filter((row) => row.text === true) + const liveCheckedData = specialNoteList.filter((row) => row.check === true) const data = [] for (let ele of liveCheckedData) { @@ -136,7 +184,7 @@ export default function Estimate({ params }) { } const newData = data.join('、') - setState({ estimateOption: newData }) + setEstimateContextState({ estimateOption: newData }) } }, [specialNoteList]) @@ -146,23 +194,27 @@ export default function Estimate({ params }) { event.stopPropagation() } - // 추가한 첨부파일 state에 넣기 + // 추가한 첨부파일 estimateContextState에 넣기 useEffect(() => { if (isNotEmptyArray(files)) { files.map((row) => { - setState({ fileList: row.data }) + setEstimateContextState({ fileList: row.data }) }) } else { - setState({ fileList: [] }) + setEstimateContextState({ fileList: [] }) } }, [files]) //상세에서 내려온 첨부파일 set 만들기 useEffect(() => { - if (isNotEmptyArray(state.fileList)) { - setOriginFiles(state.fileList) + if (isNotEmptyArray(estimateContextState.fileList)) { + //드래그영역 비워주기 + setFiles([]) + setOriginFiles(estimateContextState.fileList) } - }, [state?.fileList]) + + // setFiles([]) + }, [estimateContextState?.fileList]) // 기존첨부파일 삭제 const deleteOriginFile = async (objectNo, no) => { @@ -171,53 +223,23 @@ export default function Estimate({ params }) { objectNo: objectNo, no: no, } - await promisePost({ url: 'api/file/fileDelete', data: delParams }).then((res) => { if (res.status === 204) { setOriginFiles(originFiles.filter((file) => file.objectNo === objectNo && file.no !== no)) - setState({ + setEstimateContextState({ fileList: originFiles.filter((file) => file.objectNo === objectNo && file.no !== no), }) } }) } - //아이템 목록 + //가격표시 option 목록 최초세팅 && 주문분류 변경시 useEffect(() => { - if (isNotEmptyArray(state.itemList)) { - setItemList(state.itemList) - } - }, [state?.itemList]) - - //가격표시 option 최초세팅 - useEffect(() => { - const param = { - saleStoreId: session.storeId, - sapSalesStoreCd: session.custCd, - docTpCd: state?.estimateType, - } - - const apiUrl = `/api/estimate/price/store-price-list?${queryStringFormatter(param)}` - get({ url: apiUrl }).then((res) => { - if (isNotEmptyArray(res?.data)) { - setStorePriceList(res.data) - } - }) - }, [state?.estimateType]) - - useEffect(() => { - if (state.priceCd) { - setTempPriceCd(state.priceCd) - } - }, [state?.priceCd]) - - //가격표시 option 변경시 - useEffect(() => { - if (tempPriceCd !== '') { + if (estimateContextState.estimateType !== '') { const param = { saleStoreId: session.storeId, sapSalesStoreCd: session.custCd, - docTpCd: tempPriceCd, + docTpCd: estimateContextState?.estimateType, } const apiUrl = `/api/estimate/price/store-price-list?${queryStringFormatter(param)}` @@ -226,33 +248,136 @@ export default function Estimate({ params }) { setStorePriceList(res.data) } }) - } - }, [tempPriceCd]) - //Pricing 버튼 - const handlePricing = async (priceCd) => { + if (estimateContextState.estimateType === 'YJSS') { + if (specialNoteList.length > 0) { + specialNoteList.map((item) => { + if (item.code === 'ATTR002') { + item.check = false + } + }) + } + + //YJSS UNIT_PIRCE로 프라이싱 실행 + if (handlePricingFlag) { + handlePricing('UNIT_PRICE') + } + } else { + if (specialNoteList.length > 0) { + specialNoteList.map((item) => { + if (item.code === 'ATTR002') { + item.check = true + } + }) + } + + //비워주기 + setEstimateContextState({ + pkgAsp: '0', + pkgTotPrice: '0', + }) + + //YJOD로 돌아가도 UNIT_PRICE로 프라이싱 실행해서 정가로 셋팅 + if (handlePricingFlag) { + handlePricing('UNIT_PRICE') + } + } + + setItemChangeYn(true) + } + }, [estimateContextState?.estimateType]) + + useEffect(() => { + if (estimateContextState?.priceCd) { + setShowPriceCd(estimateContextState.priceCd) + } + }, [estimateContextState?.priceCd]) + + //가격 표시 option 변경 이벤트 + const onChangeStorePriceList = (priceCd) => { const param = { saleStoreId: session.storeId, sapSalesStoreCd: session.custCd, - docTpCd: state.estimateType, - priceCd: session.storeLvl === '1' ? tempPriceCd : priceCd, - itemIdList: state.itemList, //아이템 최초정보로 호출 + docTpCd: priceCd, + } + + //프라이싱 했을때 priceCd setEstimateContextState + //화면에 보여지는 값은 showPriceCd로 관리 + setShowPriceCd(priceCd) + + const apiUrl = `/api/estimate/price/store-price-list?${queryStringFormatter(param)}` + get({ url: apiUrl }).then((res) => { + if (isNotEmptyArray(res?.data)) { + setStorePriceList(res.data) + } + }) + } + + //Pricing 버튼 + const handlePricing = async (showPriceCd) => { + const param = { + saleStoreId: session.storeId, + sapSalesStoreCd: session.custCd, + docTpCd: estimateContextState.estimateType, + priceCd: showPriceCd, + itemIdList: estimateContextState.itemList.filter((item) => item.delFlg === '0' && item.paDispOrder === null), + } + + if (param.itemIdList.length > 0) { + let pass = true + + param.itemIdList.map((item) => { + if (item.itemId === '') { + pass = false + } + }) + + if (!pass) { + //Pricing이 누락된 아이템이 있습니다. Pricing을 진행해주세요. + return alert(getMessage('estimate.detail.showPrice.pricingBtn.noItemId')) + } } - // console.log('프라이싱파람::', param) await promisePost({ url: '/api/estimate/price/item-price-list', data: param }).then((res) => { + let updateList = [] if (res) { if (res.status === 200) { const data = res.data + //기존itemList랑 프라이싱결과랑 비교해서 단가만 업뎃 서로 갯수가 안맞을 수 있음 없는 itemId면 unitPrice 0으로 + //itemId로 비교해서 salePrice만 업데이트 if (data.result.code === 200) { + setEstimateContextState({ + pkgAsp: data?.data?.pkgUnitPrice, + }) + //주택PKG단가 체인지 이벤트 발생시키기 + onChangePkgAsp(data.data.pkgUnitPrice) + if (isNotEmptyArray(data.data2)) { - //아이템쪽 다 새로고침............ - //성공후.. - //기존itemList랑 프라이싱결과랑 비교해서 단가만 업뎃 서로 갯수가 안맞을 수 있음 없는 itemId면 unitPrice 0으로 - //itemId로 비교해서 단가정보만 업데이트 - setState({ - priceCd: session.storeLvl === '1' ? tempPriceCd : priceCd, + estimateContextState.itemList.map((item) => { + let checkYn = false + data.data2.map((item2) => { + if (item2) { + if (item2.itemId === item.itemId) { + updateList.push({ + ...item, + salePrice: item2.unitPrice === null ? '0' : item2.unitPrice, + saleTotPrice: (item.amount * item2.unitPrice).toString(), + }) + checkYn = true + } + } + }) + + if (!checkYn) { + updateList.push({ ...item, salePrice: '0', saleTotPrice: '0' }) + } }) + setEstimateContextState({ + priceCd: showPriceCd, + itemList: updateList, + }) + + setItemChangeYn(true) } } } @@ -260,10 +385,432 @@ export default function Estimate({ params }) { }) } - // 제품 추가 테스트 - const addItemTest = () => { - const newItemDispOrder = (Math.max(...state.itemList.map((item) => item.dispOrder)) + 1) * 100 - console.log('newItemDispOrder::', newItemDispOrder) + const getAbledItems = (items) => { + return items.filter((items) => items.paDispOrder === null) + } + + const onChangeSelectAll = (e) => { + if (e.target.checked) { + const allCheckedSelection = new Set(getAbledItems(estimateContextState.itemList).map((item) => item.dispOrder)) + + setSelection(allCheckedSelection) + } else { + setSelection(new Set()) + } + } + + const isSelectedAll = () => { + return selection.size === getAbledItems(estimateContextState.itemList).length + } + + //row 체크박스 컨트롤 + const onChangeSelect = (dispOrder) => { + const newSelection = new Set(selection) + if (newSelection.has(dispOrder)) { + newSelection.delete(dispOrder) + } else { + newSelection.add(dispOrder) + } + + setSelection(newSelection) + } + + //주택PKG input 변경 + const onChangePkgAsp = (value) => { + if (estimateContextState.estimateType === 'YJSS') { + let pkgAsp = Number(value.replaceAll(',', '')) + if (isNaN(pkgAsp)) { + pkgAsp = 0 + } else { + pkgAsp = pkgAsp.toLocaleString() + } + //현재 PKG용량값 가져오기 + + let totVolKw = estimateContextState.totVolKw * 1000 + let pkgTotPrice = pkgAsp.replaceAll(',', '') * totVolKw * 1000 + + setEstimateContextState({ + pkgAsp: pkgAsp, + pkgTotPrice: pkgTotPrice.toFixed(3), + }) + //아이템들 중 조건에 맞는애들 뽑아서 상단 공급가액 부가세 총액 수정 + setItemChangeYn(true) + } + } + + // 수량 변경 + const onChangeAmount = (value, dispOrder, index) => { + //itemChangeFlg = 1, partAdd = 0 셋팅 + let amount = Number(value.replace(/[^0-9]/g, '').replaceAll(',', '')) + if (isNaN(amount)) { + amount = '0' + } else { + amount = amount.toLocaleString() + } + + let updateList = [] + let updates = {} + updates.amount = amount + updates.itemChangeFlg = '1' + updates.partAdd = '0' + updates.saleTotPrice = (Number(amount.replaceAll(',', '')) * estimateContextState.itemList[index].salePrice.replaceAll(',', '')).toLocaleString() + + updateList = estimateContextState.itemList.map((item) => { + if (item.dispOrder === dispOrder) { + return { ...item, ...updates } + } else if (item.paDispOrder === dispOrder) { + return { ...item, ...updates, amount: (item.bomAmount * amount?.replaceAll(',', '')).toLocaleString(), saleTotPrice: '0' } + } else { + return item + } + }) + + setEstimateContextState({ + itemList: updateList, + }) + + setItemChangeYn(true) + } + + // 단가 변경 + const onChangeSalePrice = (value, dispOrder, index) => { + //itemChangeFlg, partAdd 받아온 그대로 + let salePrice = Number(value.replace(/[^0-9]/g, '').replaceAll(',', '')) + if (isNaN(salePrice)) { + salePrice = 0 + } else { + salePrice = salePrice.toLocaleString() + } + let updateList = [] + let updates = {} + updates.salePrice = salePrice + updates.saleTotPrice = (Number(salePrice.replaceAll(',', '')) * estimateContextState.itemList[index].amount.replaceAll(',', '')).toLocaleString() + + updateList = estimateContextState.itemList.map((item) => { + if (item.dispOrder === dispOrder) { + return { ...item, ...updates } + } else { + return item + } + }) + + setEstimateContextState({ + itemList: updateList, + }) + + setItemChangeYn(true) + } + + // 아이템 자동완성 검색시 아이템 추가/변경시 + const onChangeDisplayItem = (itemId, dispOrder, index) => { + const param = { + itemId: itemId, + } + const apiUrl = `/api/display-item/item-detail?${queryStringFormatter(param)}` + let updateList = [] + let updates = {} + get({ url: apiUrl }).then((res) => { + console.log('아이템디테일::::::::', res) + updates.objectNo = objectNo + updates.planNo = planNo + updates.itemId = res.itemId + updates.itemNo = res.itemNo + updates.itemName = res.itemName + updates.itemChangeFlg = '1' //무조건 1 + updates.partAdd = '1' //무조건1 NEW + updates.fileUploadFlg = res.fileUploadFlg + updates.unit = res.unit + updates.unitPrice = res.salePrice //unitPrice도 salePrice로 + updates.moduleFlg = res.moduleFlg + updates.pkgMaterialFlg = res.pkgMaterialFlg + updates.pnowW = res.pnowW + updates.salePrice = res.salePrice + // updates.salePrice = '' + updates.specification = res.specification + updates.unit = res.unit + updates.specialNoteCd = res.spnAttrCds + updates.itemGroup = res.itemGroup + updates.delFlg = '0' // 삭제플래그 0 + updates.saleTotPrice = (res.salePrice * estimateContextState.itemList[index].amount).toString() + // updates.saleTotPrice = '' + updates.amount = '' + + if (estimateContextState.estimateType === 'YJSS') { + if (res.pkgMaterialFlg === '0') { + updates.showSalePrice = '0' + updates.showSaleTotPrice = '0' + } + } + //104671 + let bomList = res.itemBomList + + updateList = estimateContextState.itemList.map((item) => { + if (item.dispOrder === dispOrder) { + if (item?.addFlg) { + return { ...item, ...updates, saleTotPrice: '' } + } else { + return { ...item, ...updates, salePrice: '', saleTotPrice: '' } + } + } else if (item.paDispOrder === dispOrder) { + //봄제품을 바꿨을떄 + return { ...item, delFlg: '1' } + } else { + return item + } + }) + //paDispOrder + if (bomList) { + bomList.map((bomItem, index) => { + let maxItemDispOrder = Math.max(...estimateContextState.itemList.map((item) => item.dispOrder)) + if (maxItemDispOrder == dispOrder) { + bomItem.dispOrder = (index + 1 + maxItemDispOrder).toString() + bomItem.paDispOrder = dispOrder + bomItem.salePrice = '0' + bomItem.saleTotPrice = '0' + bomItem.unitPrice = '0' + } else { + bomItem.dispOrder = (index + 1 + Number(dispOrder)).toString() + bomItem.paDispOrder = dispOrder + bomItem.salePrice = '0' + bomItem.saleTotPrice = '0' + bomItem.unitPrice = '0' + } + + bomItem.delFlg = '0' + bomItem.objectNo = objectNo + bomItem.planNo = planNo + }) + + setEstimateContextState({ + itemList: [...updateList, ...bomList], + }) + } else { + setEstimateContextState({ + itemList: updateList, + }) + } + + setItemChangeYn(true) + }) + } + + //제품 삭제 + const removeItem = () => { + const array = [...selection] + let delList = [] + estimateContextState.itemList.filter((row) => { + array.map((row2) => { + if (row2 === row.dispOrder) { + delList.push({ ...row }) + } + if (row2 === row.paDispOrder) { + delList.push({ ...row }) + } + }) + }) + + const updateList = estimateContextState.itemList.map((item) => { + const isDeleted = delList.some((row) => item.delFlg === '1' || item.dispOrder === row.dispOrder) + return { + ...item, + delFlg: isDeleted ? '1' : '0', + } + }) + + let delCnt = 0 + updateList.map((item) => { + if (item.delFlg === '1') { + delCnt++ + } + }) + + if (delCnt === updateList.length) { + return alert(getMessage('estimate.detail.save.requiredItem')) + } + + setEstimateContextState({ + itemList: updateList, + }) + + setSelection(new Set()) + setItemChangeYn(true) + } + + useEffect(() => { + if (itemChangeYn) { + let totAmount = 0 + let totVolKw = 0 + let supplyPrice = 0 + let vatPrice = 0 + let totPrice = 0 + let addSupplyPrice = 0 + if (estimateContextState.estimateType === 'YJOD') { + estimateContextState.itemList.sort((a, b) => { + return a.dispOrder - b.dispOrder + }) + console.log('YJOD 토탈만들어주기::::::::::', estimateContextState.itemList) + + let pushData = [] + let uniquSet = new Set() + + estimateContextState.itemList.forEach((item) => { + if (item.delFlg === '1') { + if (item.specialNoteCd) { + let splitData = item.specialNoteCd.split('、') + splitData.forEach((note) => { + if (!uniquSet.has(note)) { + uniquSet.add(note) + pushData.push(note) + } + }) + + setSpecialNoteFirstFlg(false) + } + } + }) + let estimateOption = estimateContextState?.estimateOption?.split('、') + // console.log('오리진::', estimateOption) + let removeEstimateOption = estimateOption.filter((option) => !pushData.includes(option)) + // console.log('남길거?? ', removeEstimateOption) + let removeEstimateOptionString = removeEstimateOption.join('、') + + // console.log('SpecialNoteLis::', specialNoteList) + + // specialNoteList.map((row) => { + // row.check = false + // estimateOption.map((row2) => { + // if (row.pkgYn === '0') { + // if (row2 === row.code) { + // row.check = true + // } + // } else { + // // console.log('지울꺼::', removeEstimateOptionString) + // if (row.code.includes(removeEstimateOptionString)) { + // // console.log('removeEstimateOption::::', removeEstimateOption) + // // row.check = false + // return + // } else { + // // console.log('제품가대세팅::::', row.code) + // // row.check = true + // } + // } + // }) + // }) + + estimateContextState.itemList.map((item) => { + delete item.showSalePrice + delete item.showSaleTotPrice + if (item.delFlg === '0') { + let amount = Number(item?.amount?.replace(/[^0-9]/g, '').replaceAll(',', '')) + if (isNaN(amount)) { + amount = '0' + } + let price = Number(item?.saleTotPrice?.replaceAll(',', '')) + if (isNaN(price)) { + price = 0 + } + if (item.moduleFlg === '1') { + //용량(Kw)은 모듈플래그 1만 합산 + const volKw = (item.pnowW * amount) / 1000 + // const volKw = item.pnowW * amount + totVolKw += volKw + } + // const price + totAmount += amount + supplyPrice += price + } + }) + + vatPrice = supplyPrice * 0.1 + totPrice = supplyPrice + vatPrice + + setEstimateContextState({ + totAmount: totAmount, + totVolKw: totVolKw.toFixed(3), + supplyPrice: supplyPrice.toFixed(3), + vatPrice: vatPrice.toFixed(3), + totPrice: totPrice.toFixed(3), + }) + } else { + //YJSS + console.log('YJSS 토탈만들어주기::::::::::', estimateContextState.itemList) + estimateContextState.itemList.sort((a, b) => { + return a.dispOrder - b.dispOrder + }) + estimateContextState.itemList.map((item) => { + if (item.delFlg === '0') { + let amount = Number(item.amount?.replace(/[^0-9]/g, '').replaceAll(',', '')) + let salePrice = Number(item.salePrice?.replaceAll(',', '')) + let saleTotPrice = Number(item.saleTotPrice?.replaceAll(',', '')) + + if (isNaN(amount)) { + amount = '0' + } + + if (isNaN(saleTotPrice)) { + saleTotPrice = 0 + } + + if (isNaN(salePrice)) { + salePrice = 0 + } + if (item.moduleFlg === '1') { + //용량(Kw)은 모듈플래그 1만 합산 + const volKw = (item.pnowW * amount) / 1000 + // const volKw = item.pnowW * amount + totVolKw += volKw + } + setEstimateContextState({ + pkgTotPrice: estimateContextState.pkgAsp.replaceAll(',', '') * totVolKw * 1000, + }) + //pkgTotPrice + // const saleTotPrice + totAmount += amount + if (item.pkgMaterialFlg === '1') { + const pkgPrice = amount * salePrice + //다시계산하기 + //YJSS는 PKG제외상품들만(1) 모아서 수량 * 단가를 공급가액(supplyPrice)에 추가로 더해줌 + addSupplyPrice += pkgPrice + } + + if (!item.paDispOrder) { + //paDispOrder + if (item.pkgMaterialFlg === '0') { + item.showSalePrice = '0' + item.showSaleTotPrice = '0' + } + } + } + }) + supplyPrice = addSupplyPrice + Number(estimateContextState.pkgTotPrice / 1000) + vatPrice = supplyPrice * 0.1 + totPrice = supplyPrice + vatPrice + setEstimateContextState({ + totAmount: totAmount, + totVolKw: totVolKw.toFixed(3), + supplyPrice: supplyPrice.toFixed(3), + vatPrice: vatPrice.toFixed(3), + totPrice: totPrice.toFixed(3), + }) + } + + setItemChangeYn(false) + } + }, [itemChangeYn, estimateContextState.itemList]) + + //안건명 인풋 변경 + const handleBlurObjectName = (e) => { + setEstimateContextState({ objectName: e.target.value }) + } + + //담당자 인풋 변경 + const handleBlurCharger = (e) => { + setEstimateContextState({ charger: e.target.value }) + } + + //비고 인풋 변경 + const handleBlurRemarks = (e) => { + setEstimateContextState({ remarks: e.target.value }) } return ( @@ -281,17 +828,21 @@ export default function Estimate({ params }) {
{getMessage('estimate.detail.docNo')}
-
{state.docNo}
+
{estimateContextState.docNo}
{getMessage('estimate.detail.drawingEstimateCreateDate')}
- {state?.drawingEstimateCreateDate ? `${dayjs(state.drawingEstimateCreateDate).format('YYYY.MM.DD')}` : ''} + {estimateContextState?.drawingEstimateCreateDate + ? `${dayjs(estimateContextState.drawingEstimateCreateDate).format('YYYY.MM.DD')}` + : ''}
{getMessage('estimate.detail.lastEditDatetime')}
-
{state?.lastEditDatetime ? `${dayjs(state.lastEditDatetime).format('YYYY.MM.DD HH:mm')}` : ''}
+
+ {estimateContextState?.lastEditDatetime ? `${dayjs(estimateContextState.lastEditDatetime).format('YYYY.MM.DD HH:mm')}` : ''} +
@@ -317,7 +868,7 @@ export default function Estimate({ params }) { {/* 1차 판매점명 */} {getMessage('estimate.detail.saleStoreId')} - {state?.firstSaleStoreName} + {estimateContextState?.firstSaleStoreName} {/* 견적일 */} {getMessage('estimate.detail.estimateDate')} * @@ -331,22 +882,14 @@ export default function Estimate({ params }) { {/* 2차 판매점명 */} {getMessage('estimate.detail.otherSaleStoreId')} - {state?.agencySaleStoreName} + {estimateContextState?.agencySaleStoreName} {/* 담당자 */} {getMessage('estimate.detail.receiveUser')} *
- { - //담당자 charger - setState({ charger: e.target.value }) - }} - /> +
@@ -358,15 +901,7 @@ export default function Estimate({ params }) {
- { - //안건명 objectName - setState({ objectName: e.target.value }) - }} - /> +
{ - setState({ estimateType: e.target.value }) + setHandlePricingFlag(true) + setEstimateContextState({ estimateType: e.target.value }) }} /> - +
@@ -441,25 +978,25 @@ export default function Estimate({ params }) { {/* 지붕재・사양시공 최대4개*/} {getMessage('estimate.detail.roofCns')} - {state?.roofMaterialIdMulti?.split('、').map((row, index) => { + {estimateContextState?.roofMaterialIdMulti?.split('、').map((row, index) => { //지붕재 let roofList = row - let roofListLength = state?.roofMaterialIdMulti?.split('、').length + let roofListLength = estimateContextState?.roofMaterialIdMulti?.split('、').length let style = 'mb5' if (roofListLength == index + 1) { style = '' } //사양시공 - let constructSpecificationMulti = state?.constructSpecificationMulti?.split('、') + let constructSpecificationMulti = estimateContextState?.constructSpecificationMulti?.split('、') return ( <> -
-
- +
+
+
- +
@@ -472,15 +1009,7 @@ export default function Estimate({ params }) { {getMessage('estimate.detail.remarks')}
- { - //비고 - setState({ remarks: e.target.value }) - }} - /> +
@@ -495,11 +1024,28 @@ export default function Estimate({ params }) { { - setState({ + setEstimateContextState({ fileFlg: e.target.checked ? '1' : '0', }) + if (e.target.checked) { + if (specialNoteList.length > 0) { + specialNoteList.map((item) => { + if (item.code === 'ATTR019') { + item.check = true + } + }) + } + } else { + if (specialNoteList.length > 0) { + specialNoteList.map((item) => { + if (item.code === 'ATTR019') { + item.check = false + } + }) + } + } }} />
{/* 첨부파일 목록 시작 */} -
- - - - - - - - - + + +
{getMessage('estimate.detail.header.fileList2')} -
-
    - {originFiles.length > 0 && - originFiles.map((originFile) => { + {originFiles.length > 0 && ( +
    + + + + + + + + + - - -
    {getMessage('estimate.detail.header.fileList2')} +
    +
      + {originFiles.map((originFile) => { return ( -
    • - +
    • + handleEstimateFileDownload(originFile)}> {originFile.faileName} - +
    • ) })} -
    -
    -
    -
    +
+
+
+
+ )} {/* 첨부파일 목록 끝 */} {/* 파일첨부 끝 */} {/* 견적특이사항 시작 */} @@ -577,25 +1131,28 @@ export default function Estimate({ params }) { specialNoteList.map((row) => { return (
{ - settingShowContent(row.code, event) + // settingShowContent(row.code, event) }} >
{ setSpecialNoteList((specialNote) => - specialNote.map((temp) => (temp.code === row.code ? { ...temp, text: !temp.text } : temp)), + specialNote.map((temp) => (temp.code === row.code ? { ...temp, check: !temp.check } : temp)), ) settingShowContent(row.code, event) }} /> - +
) @@ -605,12 +1162,34 @@ export default function Estimate({ params }) {
{specialNoteList.map((row) => { if (row.code === showContentCode) { - return ( -
-
{row.codeNm}
-
-
- ) + let showcontent = popShowSpecialNoteList.find((item) => { + return item.code === showContentCode + }) + + if (isObjectNotEmpty(showcontent)) { + return ( +
+
{showcontent.codeNm}
+
+
+ ) + } else { + let pushData = [] + popShowSpecialNoteList.map((item) => { + let option = showContentCode.split('、') + option.map((item2) => { + if (item.code === item2) { + pushData.push(item) + } + }) + }) + return pushData.map((item) => ( +
+
{item.codeNm}
+
+
+ )) + } } })}
@@ -630,29 +1209,29 @@ export default function Estimate({ params }) {
-
{getMessage('estimate.detail.sepcialEstimateProductInfo.totPcs')}
-
74
+
{getMessage('estimate.detail.sepcialEstimateProductInfo.totAmount')}
+
{convertNumberToPriceDecimal(estimateContextState?.totAmount)}
-
{getMessage('estimate.detail.sepcialEstimateProductInfo.vol')}
-
8300
+
{getMessage('estimate.detail.sepcialEstimateProductInfo.totVolKw')}
+
{convertNumberToPriceDecimalToFixed(estimateContextState?.totVolKw, 2)}
-
{getMessage('estimate.detail.sepcialEstimateProductInfo.netAmt')}
-
6,798,900
+
{getMessage('estimate.detail.sepcialEstimateProductInfo.supplyPrice')}
+
{convertNumberToPriceDecimal(estimateContextState?.supplyPrice)}
-
{getMessage('estimate.detail.sepcialEstimateProductInfo.vat')}
-
679,890
+
{getMessage('estimate.detail.sepcialEstimateProductInfo.vatPrice')}
+
{convertNumberToPriceDecimal(estimateContextState?.vatPrice)}
{getMessage('estimate.detail.sepcialEstimateProductInfo.totPrice')}
-
7,478,790
+
{convertNumberToPriceDecimal(estimateContextState?.totPrice)}
{/* YJOD면 아래영역 숨김 */} -
+
@@ -670,13 +1249,20 @@ export default function Estimate({ params }) { - + - +
- + { + onChangePkgAsp(e.target.value) + }} + />
{getMessage('estimate.detail.sepcialEstimateProductInfo.pkgWeight')}{getMessage('estimate.detail.sepcialEstimateProductInfo.calcFormula1')}{convertNumberToPriceDecimalToFixed(estimateContextState?.totVolKw, 3)} {getMessage('estimate.detail.sepcialEstimateProductInfo.pkgPrice')}{getMessage('estimate.detail.sepcialEstimateProductInfo.calcFormula2')}{convertNumberToPriceDecimal(estimateContextState?.pkgTotPrice)}
@@ -689,23 +1275,27 @@ export default function Estimate({ params }) {
{session?.storeLvl === '1' ? ( ) : ( - )}
- @@ -748,19 +1344,20 @@ export default function Estimate({ params }) { - + - - - - - + + + + + @@ -774,80 +1371,121 @@ export default function Estimate({ params }) { - {itemList.length > 0 && - itemList.map((item, index) => { - return ( - - - - + - + + - - - + - - - ) + + + + + + + ) + } else { + return null + } })}
-
- + {/*
*/} +
+
-
- - -
-
{item?.dispOrder * 100} -
-
-
+
+ onChangeSelect(item.dispOrder)} + checked={!!selection.has(item.dispOrder)} /> +
- {item?.itemChangeFlg === '1' && ( -
- -
- )} - -
-
-
{item?.itemNo}
-
- {item?.fileUploadFlg === '1' && } - {item?.specialNoteCd && ( -
{item?.dispOrder} +
+
+
-
- -
-
{item.unit} -
-
- +
+
+
{item?.itemNo}
+
+ {item?.fileUploadFlg === '1' && } + {item?.specialNoteCd && ( + + )} +
- {/*
- OPEN아이콘 처리 -
*/} - -
{convertNumberToPriceDecimal(item?.saleTotPrice)}
+
+ { + onChangeAmount(e.target.value, item.dispOrder, index) + }} + maxLength={6} + /> +
+
{item.unit} +
+
+ { + onChangeSalePrice(e.target.value, item.dispOrder, index) + }} + maxLength={12} + /> +
+ {/*
+ +
*/} +
+
+ {convertNumberToPriceDecimal(item?.showSaleTotPrice === '0' ? null : item?.saleTotPrice?.replaceAll(',', ''))} +
@@ -860,7 +1498,7 @@ export default function Estimate({ params }) {
{productFeaturesPopupOpen && ( diff --git a/src/components/estimate/popup/DocDownOptionPop.jsx b/src/components/estimate/popup/DocDownOptionPop.jsx index 0c859c59..d6b22eb2 100644 --- a/src/components/estimate/popup/DocDownOptionPop.jsx +++ b/src/components/estimate/popup/DocDownOptionPop.jsx @@ -6,36 +6,88 @@ import { useRecoilValue } from 'recoil' import { floorPlanObjectState } from '@/store/floorPlanObjectAtom' export default function DocDownOptionPop({ planNo, setEstimatePopupOpen }) { - // console.log('플랜번호::::::::::::', planNo) const { getMessage } = useMessage() - const { promiseGet } = useAxios() + const { promisePost } = useAxios() + //EXCEL, PDF 구분 + const [schDownload, setSchDownload] = useState('EXCEL') //다운로드 파일 EXCEL const [schUnitPriceFlg, setSchUnitPriceFlg] = useState('0') - //견적제출서 표시명 const [schDisplayFlg, setSchSchDisplayFlg] = useState('0') - //가대 중량표 포함 - const [schWeightFlg, setSchWeightFlg] = useState('0') + //가대 중량표 포함(포함:1 미포함 : 0) + const [schWeightFlg, setSchWeightFlg] = useState('1') //도면/시뮬레이션 파일 포함 - const [schDrawingFlg, setSchDrawingFlg] = useState('0') + const [schDrawingFlg, setSchDrawingFlg] = useState('1') // recoil 물건번호 const objectRecoil = useRecoilValue(floorPlanObjectState) //문서 다운로드 const handleFileDown = async () => { - // console.log('물건번호:::', objectRecoil.floorPlanObjectNo) - // console.log('planNo::', planNo) - // 고른 옵션값들 - //0 : 견적가 Excel 1 : 정가용Excel 2: 견적가 PDF 3 :정가용PDF - // console.log(schUnitPriceFlg) - // console.log(schDisplayFlg) - // console.log(schWeightFlg) - // console.log(schDrawingFlg) const url = '/api/estimate/excel-download' - const params = {} + let sendUnitPriceFlg + if (schUnitPriceFlg === '0') { + sendUnitPriceFlg = '0' + } else if (schUnitPriceFlg === '1') { + sendUnitPriceFlg = '1' + } else if (schUnitPriceFlg === '2') { + sendUnitPriceFlg = '0' + } else { + sendUnitPriceFlg = '1' + } + + //schDrawingFlg 선택값은 의미없어짐 + //가대중량표 포함, 도면/시뮬레이션 파일 포함 선택값에따라 schDrawingFlg에 |구분자로 보냄 + //SchDrawingFlg (1 : 견적서,2 : 발전시뮬레이션, 3 : 도면, 4 : 가대) + // ex) 1|2|3|4 + let defaultSchDrawingFlg = '1' + if (schWeightFlg === '1') { + defaultSchDrawingFlg = defaultSchDrawingFlg.concat('|', '4') + } + if (schDrawingFlg === '1') { + defaultSchDrawingFlg = defaultSchDrawingFlg.concat('|', '2', '|', '3') + } + + const params = { + objectNo: objectRecoil.floorPlanObjectNo, + planNo: planNo, + schDownload: schDownload, + schUnitPriceFlg: sendUnitPriceFlg, + schDisplayFlg: schDisplayFlg, + schWeightFlg: schWeightFlg, + schDrawingFlg: defaultSchDrawingFlg, + pwrGnrSimType: 'D', //default 화면에 안보여줌 + } + const options = { responseType: 'blob' } + await promisePost({ url: url, data: params, option: options }) + .then((resultData) => { + if (resultData) { + let fileName = 'unknow' + const blob = new Blob([resultData.data], { type: resultData.headers['content-type'] || 'application/octet-stream' }) + const fileUrl = window.URL.createObjectURL(blob) + + const link = document.createElement('a') + link.href = fileUrl + + //서버에서 내려오는 파일명 + const contentDisposition = resultData.headers['content-disposition'] + if (contentDisposition) { + fileName = contentDisposition.split('filename=')[1].replace(/['"]/g, '') + } + + link.download = fileName + document.body.appendChild(link) + link.click() + link.remove() + window.URL.revokeObjectURL(fileUrl) + } + }) + .catch((error) => { + console.log('::FileDownLoad Error::', error) + alert('File does not exist.') + }) } return ( @@ -74,28 +126,30 @@ export default function DocDownOptionPop({ planNo, setEstimatePopupOpen }) {
{ + setSchDownload('EXCEL') setSchUnitPriceFlg(e.target.value) }} /> - +
{ + setSchDownload('EXCEL') setSchUnitPriceFlg(e.target.value) }} /> - +
{ + setSchDownload('PDF') setSchUnitPriceFlg(e.target.value) }} /> - +
{ + setSchDownload('PDF') setSchUnitPriceFlg(e.target.value) }} /> - +
@@ -168,19 +224,6 @@ export default function DocDownOptionPop({ planNo, setEstimatePopupOpen }) {
- { - setSchWeightFlg(e.target.value) - }} - /> - -
-
+
+ { + setSchWeightFlg(e.target.value) + }} + /> + +
@@ -201,6 +257,19 @@ export default function DocDownOptionPop({ planNo, setEstimatePopupOpen }) {
+ { + setSchDrawingFlg(e.target.value) + }} + /> + +
+
-
- { - setSchDrawingFlg(e.target.value) - }} - /> - -
diff --git a/src/components/estimate/popup/EstimateCopyPop.jsx b/src/components/estimate/popup/EstimateCopyPop.jsx new file mode 100644 index 00000000..39ca5a28 --- /dev/null +++ b/src/components/estimate/popup/EstimateCopyPop.jsx @@ -0,0 +1,313 @@ +'use client' +import { useEffect, useState, useContext, useRef } from 'react' +import { useMessage } from '@/hooks/useMessage' +import { useAxios } from '@/hooks/useAxios' +import Select, { components } from 'react-select' +import { SessionContext } from '@/app/SessionProvider' +import { isEmptyArray, isObjectNotEmpty } from '@/util/common-utils' +import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController' + +export default function EstimateCopyPop({ planNo, setEstimateCopyPopupOpen }) { + const { getMessage } = useMessage() + const { get } = useAxios() + + const { handleEstimateCopy, state } = useEstimateController(planNo) + + const { session } = useContext(SessionContext) + + const [saleStoreList, setSaleStoreList] = useState([]) // 판매점 리스트 + const [favoriteStoreList, setFavoriteStoreList] = useState([]) //즐겨찾기한 판매점목록 + const [showSaleStoreList, setShowSaleStoreList] = useState([]) //보여줄 판매점목록 + const [otherSaleStoreList, setOtherSaleStoreList] = useState([]) + const [originOtherSaleStoreList, setOriginOtherSaleStoreList] = useState([]) + + const [saleStoreId, setSaleStoreId] = useState('') //선택한 1차점 + const [otherSaleStoreId, setOtherSaleStoreId] = useState('') //선택한 1차점 이외 + + const [sendPlanNo, setSendPlanNo] = useState('1') + const [copyReceiveUser, setCopyReceiveUser] = useState('') + + const ref = useRef() //2차점 자동완성 초기화용 + + useEffect(() => { + let url + let firstList + let favList + let otherList + if (session.storeId === 'T01') { + url = `/api/object/saleStore/${session?.storeId}/firstList?userId=${session?.userId}` + } else { + if (session.storeLvl === '1') { + url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=1&userId=${session?.userId}` + } else { + //T01 or 1차점만 복사버튼 노출됨으로 + // url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=1&userId=${session?.userId}` + } + } + + get({ url: url }).then((res) => { + if (!isEmptyArray(res)) { + res.map((row) => { + //자동완성 검색을 위한 필드명 셋팅 + row.value = row.saleStoreId + row.label = row.saleStoreName + }) + + if (session.storeId === 'T01') { + firstList = res + //T01을 첫번째로 정렬 + firstList.sort((a, b) => (a.saleStoreId !== 'T01') - (b.saleStoreId !== 'T01') || a.saleStoreId - b.saleStoreId) + favList = firstList.filter((row) => row.saleStoreId === 'T01' || row.priority !== 'B') + + setSaleStoreList(firstList) + setFavoriteStoreList(favList) + setShowSaleStoreList(favList) + setSaleStoreId(session?.storeId) + + // T01때 디폴트로 T01셋팅하고 하위 2차목록 조회하기 바꾸면(onSelectionChange..) 바꾼거로 2차목록 조회하기 + url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=0&userId=${session?.userId}` + get({ url: url }).then((res) => { + if (!isEmptyArray(res)) { + res.map((row) => { + row.value == row.saleStoreId + row.label = row.saleStoreName + }) + otherList = res + setOtherSaleStoreList(otherList) + setOriginOtherSaleStoreList(otherList) + } else { + setOtherSaleStoreList([]) + } + }) + } else { + if (session.storeLvl === '1') { + firstList = res + favList = res.filter((row) => row.priority !== 'B') + otherList = res.filter((row) => row.firstAgentYn === 'N') + setSaleStoreList(firstList) + setFavoriteStoreList(firstList) + setShowSaleStoreList(firstList) + setSaleStoreId(firstList[0].saleStoreId) + + setOtherSaleStoreList(otherList) + } else { + //T01 or 1차점만 복사버튼 노출됨으로 + } + } + } + }) + }, []) + + useEffect(() => { + if (planNo) { + setSendPlanNo(planNo) + } + }, [planNo]) + + useEffect(() => { + if (state?.charger) { + setCopyReceiveUser(state.charger) + } + }, [state.charger]) + + //T01 1차점 자동완성 인풋때 목록 변환 + const onInputChange = (key) => { + if (key !== '') { + setShowSaleStoreList(saleStoreList) + } else { + setShowSaleStoreList(favoriteStoreList) + } + } + + // 1차점 변경 이벤트 + const onSelectionChange = (key) => { + if (isObjectNotEmpty(key)) { + if (key.saleStoreId === saleStoreId) { + return + } + } + + if (isObjectNotEmpty(key)) { + setSaleStoreId(key.saleStoreId) + const url = `/api/object/saleStore/${key.saleStoreId}/list?firstFlg=0&userId=${session?.userId}` + let otherList + get({ url: url }).then((res) => { + if (!isEmptyArray(res)) { + res.map((row) => { + row.value = row.saleStoreId + row.label = row.saleStoreName + }) + + otherList = res + setOtherSaleStoreList(otherList) + setOtherSaleStoreId('') + } else { + setOtherSaleStoreId('') + setOtherSaleStoreList([]) + } + }) + } else { + setSaleStoreId('') + //otherSaleStoreId는 onSelectionChange2에서 초기화됨 + setOtherSaleStoreList(originOtherSaleStoreList) + handleClear() + } + } + + // 2차점 변경 이벤트 + const onSelectionChange2 = (key) => { + if (isObjectNotEmpty(key)) { + if (key.saleStoreId === otherSaleStoreId) { + return + } + } + + if (isObjectNotEmpty(key)) { + setOtherSaleStoreId(key.saleStoreId) + } else { + setOtherSaleStoreId('') + } + } + + //2차점 자동완성 초기화 + const handleClear = () => { + if (ref.current) { + ref.current.clearValue() + } + } + + return ( +
+
+
+
+

{getMessage('estimate.detail.estimateCopyPopup.title')}

+ +
+
+
+
{getMessage('estimate.detail.estimateCopyPopup.explane')}
+
+
+
+ {getMessage('estimate.detail.estimateCopyPopup.label.saleStoreId')} * +
+ {session.storeId === 'T01' && ( +
+
+ x.saleStoreName} + getOptionValue={(x) => x.saleStoreId} + isClearable={false} + isDisabled={session?.storeLvl !== '1' ? true : session?.storeId !== 'T01' ? true : false} + value={showSaleStoreList.filter(function (option) { + return option.saleStoreId === saleStoreId + })} + /> +
+
{saleStoreId}
+
+ )} +
+
+
{getMessage('estimate.detail.estimateCopyPopup.label.otherSaleStoreId')}
+
+
+ { + setCopyReceiveUser(e.target.value) + }} + /> +
+
+
+
+
+ + +
+
+
+
+
+ ) +} diff --git a/src/components/estimate/popup/ProductFeaturesPop.jsx b/src/components/estimate/popup/ProductFeaturesPop.jsx index 3940065d..5b1af6aa 100644 --- a/src/components/estimate/popup/ProductFeaturesPop.jsx +++ b/src/components/estimate/popup/ProductFeaturesPop.jsx @@ -1,13 +1,13 @@ 'use client' import { useEffect, useState } from 'react' import { useMessage } from '@/hooks/useMessage' -export default function ProductFeaturesPop({ specialNoteList, showProductFeatureData, setProductFeaturesPopupOpen }) { +export default function ProductFeaturesPop({ popShowSpecialNoteList, showProductFeatureData, setProductFeaturesPopupOpen }) { const [showSpecialNoteList, setShowSpecialNoteList] = useState([]) const { getMessage } = useMessage() useEffect(() => { let pushData = [] - specialNoteList.map((row) => { + popShowSpecialNoteList.map((row) => { let option = showProductFeatureData.split('、') option.map((row2) => { if (row.code === row2) { @@ -16,7 +16,7 @@ export default function ProductFeaturesPop({ specialNoteList, showProductFeature }) }) setShowSpecialNoteList(pushData) - }, [specialNoteList]) + }, [popShowSpecialNoteList]) return (
@@ -42,7 +42,8 @@ export default function ProductFeaturesPop({ specialNoteList, showProductFeature return (
{row.codeNm}
-
+ {/*
*/} +
) })} diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index a248412e..2202bac0 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -1,15 +1,8 @@ import { fabric } from 'fabric' import { v4 as uuidv4 } from 'uuid' import { QLine } from '@/components/fabric/QLine' -import { - distanceBetweenPoints, - findTopTwoIndexesByDistance, - getAllRelatedObjects, - getDirectionByPoint, - sortedPointLessEightPoint, - sortedPoints, -} from '@/util/canvas-util' -import { calculateAngle, drawRidgeRoof, drawShedRoof, inPolygon, toGeoJSON } from '@/util/qpolygon-utils' +import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util' +import { calculateAngle, drawGabledRoof, drawRidgeRoof, drawShedRoof, inPolygon, toGeoJSON } from '@/util/qpolygon-utils' import * as turf from '@turf/turf' import { LINE_TYPE } from '@/common/common' @@ -38,6 +31,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { this.cells = [] this.innerLines = [] this.children = [] + this.separatePolygon = [] // 소수점 전부 제거 points.forEach((point) => { @@ -131,15 +125,14 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { this.on('selected', () => { Object.keys(this.controls).forEach((controlKey) => { - if (controlKey !== 'ml' && controlKey !== 'mr') { - this.setControlVisible(controlKey, false) - } + this.setControlVisible(controlKey, false) }) + this.set({ hasBorders: false }) }) this.on('removed', () => { // const children = getAllRelatedObjects(this.id, this.canvas) - const children = this.canvas.getObjects().filter((obj) => obj.parentId === this.id && obj.name === 'lengthText') + const children = this.canvas.getObjects().filter((obj) => obj.parentId === this.id) children.forEach((child) => { this.canvas.remove(child) }) @@ -167,6 +160,10 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { }, initLines() { + // if (this.lines.length > 0) { + // return + // } + this.lines = [] this.points.forEach((point, i) => { @@ -189,6 +186,12 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { }) }, + containsPoint: function (point) { + const isInside = this.inPolygon(point) + this.set('selectable', isInside) + return isInside + }, + // 보조선 그리기 drawHelpLine() { // drawHelpLineInHexagon(this, pitch) @@ -208,7 +211,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { (gableOdd.every((type) => type === LINE_TYPE.WALLLINE.EAVES) && gableEven.every((type) => gableType.includes(type))) || (gableEven.every((type) => type === LINE_TYPE.WALLLINE.EAVES) && gableOdd.every((type) => gableType.includes(type))) ) { - console.log('박공 지붕') + drawGabledRoof(this.id, this.canvas) } else if (hasShed) { const sheds = this.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.SHED) const areLinesParallel = function (line1, line2) { @@ -263,9 +266,11 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { this.texts = [] points.forEach((start, i) => { const end = points[(i + 1) % points.length] + // planeSize: Math.round(Math.sqrt(Math.pow(newLine.x1 - newLine.x2, 2) + Math.pow(newLine.y1 - newLine.y2, 2))) * 10, + // actualSize: Math.round(Math.sqrt(Math.pow(newLine.x1 - newLine.x2, 2) + Math.pow(newLine.y1 - newLine.y2, 2))) * 10, const dx = end.x - start.x const dy = end.y - start.y - const length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(1)) * 10 + const length = Math.round(Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2))) * 10 let midPoint diff --git a/src/components/floor-plan/CanvasFrame.jsx b/src/components/floor-plan/CanvasFrame.jsx index 6a48d83b..d43afc59 100644 --- a/src/components/floor-plan/CanvasFrame.jsx +++ b/src/components/floor-plan/CanvasFrame.jsx @@ -1,6 +1,6 @@ 'use client' -import { useEffect, useRef } from 'react' +import { useContext, useEffect, useRef } from 'react' import { useRecoilValue } from 'recoil' @@ -13,6 +13,9 @@ import QContextMenu from '@/components/common/context-menu/QContextMenu' import { useCanvasConfigInitialize } from '@/hooks/common/useCanvasConfigInitialize' import { MENU } from '@/common/common' import PanelBatchStatistics from '@/components/floor-plan/modal/panelBatch/PanelBatchStatistics' +import { totalDisplaySelector } from '@/store/settingAtom' +import ImgLoad from '@/components/floor-plan/modal/ImgLoad' +import { EventContext } from '@/app/floor-plan/EventProvider' export default function CanvasFrame() { const canvasRef = useRef(null) @@ -21,7 +24,10 @@ export default function CanvasFrame() { const currentMenu = useRecoilValue(currentMenuState) const { contextMenu, handleClick } = useContextMenu() const { selectedPlan, modifiedPlanFlag, checkCanvasObjectEvent, resetModifiedPlans, currentCanvasPlan } = usePlan() - useEvent() + const totalDisplay = useRecoilValue(totalDisplaySelector) // 집계표 표시 여부 + // useEvent() + // const { initEvent } = useContext(EventContext) + // initEvent() const loadCanvas = () => { if (canvas) { @@ -72,7 +78,10 @@ export default function CanvasFrame() { MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING, MENU.MODULE_CIRCUIT_SETTING.CIRCUIT_TRESTLE_SETTING, MENU.MODULE_CIRCUIT_SETTING.PLAN_ORIENTATION, - ].includes(currentMenu) && } + ].includes(currentMenu) && + totalDisplay && } + {/* 이미지 로드 팝업 */} +
) } diff --git a/src/components/floor-plan/CanvasLayout.jsx b/src/components/floor-plan/CanvasLayout.jsx index 1604a28f..fcaabb6f 100644 --- a/src/components/floor-plan/CanvasLayout.jsx +++ b/src/components/floor-plan/CanvasLayout.jsx @@ -1,8 +1,8 @@ 'use client' -import { useContext, useEffect, useState } from 'react' +import { useContext, useEffect } from 'react' import { useRecoilValue } from 'recoil' -import CanvasFrame from './CanvasFrame' +import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider' import { useMessage } from '@/hooks/useMessage' import { useSwal } from '@/hooks/useSwal' import { usePlan } from '@/hooks/usePlan' @@ -14,15 +14,16 @@ export default function CanvasLayout({ children }) { // const { menuNumber } = props const { menuNumber } = useCanvasMenu() const { session } = useContext(SessionContext) - const [objectNo, setObjectNo] = useState('test123240822001') // 이후 삭제 필요 + const { floorPlanState } = useContext(FloorPlanContext) + const { objectNo, pid } = floorPlanState const globalLocaleState = useRecoilValue(globalLocaleStore) const { getMessage } = useMessage() const { swalFire } = useSwal() - const { plans, modifiedPlans, loadCanvasPlanData, handleCurrentPlan, handleAddPlan, handleDeletePlan } = usePlan() + const { plans, initCanvasPlans, modifiedPlans, loadCanvasPlanData, handleCurrentPlan, handleAddPlan, handleDeletePlan } = usePlan() useEffect(() => { - loadCanvasPlanData(session.userId, objectNo) + loadCanvasPlanData(session.userId, objectNo, pid) }, []) return ( @@ -36,14 +37,18 @@ export default function CanvasLayout({ children }) { onClick={() => handleCurrentPlan(session.userId, plan.id)} > - {plan.name} + {!initCanvasPlans.some((initCanvasPlans) => initCanvasPlans.id === plan.id) && 'New '} + {`Plan ${plan.ordering}`} {modifiedPlans.some((modifiedPlan) => modifiedPlan === plan.id) && ' [ M ]'} swalFire({ - text: `${plan.name} ` + getMessage('plan.message.confirm.delete'), + text: + (!initCanvasPlans.some((initCanvasPlans) => initCanvasPlans.id === plan.id) ? 'New ' : '') + + `Plan ${plan.ordering} ` + + getMessage('plan.message.confirm.delete'), type: 'confirm', confirmFn: () => { handleDeletePlan(e, plan.id) diff --git a/src/components/floor-plan/CanvasMenu.jsx b/src/components/floor-plan/CanvasMenu.jsx index e544dbec..79944044 100644 --- a/src/components/floor-plan/CanvasMenu.jsx +++ b/src/components/floor-plan/CanvasMenu.jsx @@ -1,6 +1,6 @@ 'use client' -import { useEffect, useState } from 'react' +import { useContext, useEffect, useState } from 'react' import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' @@ -33,8 +33,12 @@ import useMenu from '@/hooks/common/useMenu' import { MENU } from '@/common/common' import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController' -import { estimateState } from '@/store/floorPlanObjectAtom' +import { estimateState, floorPlanObjectState } from '@/store/floorPlanObjectAtom' import DocDownOptionPop from '../estimate/popup/DocDownOptionPop' +import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider' +import EstimateCopyPop from '../estimate/popup/EstimateCopyPop' +import { pwrGnrSimTypeState } from '@/store/simulatorAtom' +import { useAxios } from '@/hooks/useAxios' export default function CanvasMenu(props) { const { menuNumber, setMenuNumber } = props @@ -59,14 +63,17 @@ export default function CanvasMenu(props) { const { handleEstimateSubmit } = useEstimateController() const estimateRecoilState = useRecoilValue(estimateState) const [estimatePopupOpen, setEstimatePopupOpen] = useState(false) + const [estimateCopyPopupOpen, setEstimateCopyPopupOpen] = useState(false) const { getMessage } = useMessage() const { currentCanvasPlan, saveCanvas } = usePlan() const { swalFire } = useSwal() const { initEvent, addCanvasMouseEventListener, addDocumentEventListener } = useEvent() + // const { initEvent, addCanvasMouseEventListener, addDocumentEventListener } = useContext(EventContext) const commonUtils = useRecoilValue(commonUtilsState) const { commonFunctions } = useCommonUtils() const SelectOption = [{ name: '瓦53A' }, { name: '瓦53A' }] + const { floorPlanState, setFloorPlanState } = useContext(FloorPlanContext) const onClickNav = (menu) => { setMenuNumber(menu.index) @@ -161,20 +168,6 @@ export default function CanvasMenu(props) { }) } - /** - * 견적서 복사버튼 - * (견적서 번호(estimateRecoilState.docNo)가 생성된 이후 버튼 활성화 ) - * T01관리자 계정 및 1차판매점에게만 제공 - */ - - const handleEstimateCopy = () => { - // console.log('estimateRecoilState::', estimateRecoilState) - //objectNo, planNo - console.log('복사') - console.log('물건정보+도면+견적서를 모두 복사') - console.log('견적서 가격은 정가를 표시') - } - useEffect(() => { if (globalLocale === 'ko') { setAppMessageState(KO) @@ -184,7 +177,7 @@ export default function CanvasMenu(props) { }, [type, globalLocale]) useEffect(() => { - if (['2', '3'].includes(canvasSetting?.roofSizeSet?.toString())) { + if ([2, 3].some((num) => num === canvasSetting?.roofSizeSet)) { setMenuNumber(3) setType('surface') setCurrentMenu(MENU.BATCH_CANVAS.BATCH_DRAWING) @@ -195,8 +188,60 @@ export default function CanvasMenu(props) { } }, [canvasSetting]) + const checkMenuState = (menu) => { + return ([2, 3].some((num) => num === canvasSetting?.roofSizeSet) && menu.index === 2) || (menuNumber === 4 && menu.index === 2) + } + + // 발전시물레이션 Excel/PDF 다운 + const { promisePost } = useAxios(globalLocale) + const objectRecoil = useRecoilValue(floorPlanObjectState) + const pwrGnrSimTypeRecoil = useRecoilValue(pwrGnrSimTypeState) + + const { plans } = usePlan() + const plan = plans.find((plan) => plan.isCurrent === true) + + const handleExcelPdfFileDown = async (donwloadType, drawingFlg) => { + const url = '/api/estimate/excel-download' + + const params = { + objectNo: objectRecoil.floorPlanObjectNo, + planNo: plan?.id, + schDownload: donwloadType, + schDrawingFlg: drawingFlg, + pwrGnrSimType: pwrGnrSimTypeRecoil.type, + } + + const options = { responseType: 'blob' } + await promisePost({ url: url, data: params, option: options }) + .then((resultData) => { + if (resultData) { + let fileName = 'unknow' + const blob = new Blob([resultData.data], { type: resultData.headers['content-type'] || 'application/octet-stream' }) + const fileUrl = window.URL.createObjectURL(blob) + + const link = document.createElement('a') + link.href = fileUrl + + //서버에서 내려오는 파일명 + const contentDisposition = resultData.headers['content-disposition'] + if (contentDisposition) { + fileName = contentDisposition.split('filename=')[1].replace(/['"]/g, '') + } + + link.download = fileName + document.body.appendChild(link) + link.click() + link.remove() + window.URL.revokeObjectURL(fileUrl) + } + }) + .catch((error) => { + alert('File does not exist.') + }) + } + return ( -
+
num === menuNumber) ? 'active' : ''}`}>
    {canvasMenus.map((menu) => { @@ -205,11 +250,12 @@ export default function CanvasMenu(props) { key={`canvas-menu-${menu.index}`} className={`canvas-menu-item ${menuNumber === menu.index ? 'active' : ''}`} onClick={() => { - if (['2', '3'].includes(canvasSetting?.roofSizeSet?.toString()) && menu.index === 2) return + if ([2, 3].some((num) => num === canvasSetting?.roofSizeSet) && menu.index === 2) return + if (menuNumber === 4 && menu.index === 2) return onClickNav(menu) }} > - @@ -218,7 +264,7 @@ export default function CanvasMenu(props) { })}
- {menuNumber !== 6 && menuNumber !== 5 && ( + {![5, 6].some((num) => num === menuNumber) && ( <> {
@@ -235,6 +281,10 @@ export default function CanvasMenu(props) {
+ {/**/} @@ -247,7 +297,7 @@ export default function CanvasMenu(props) { handleZoom(false) }} > - {canvasZoom}% + {canvasZoom}% - {/* {estimateRecoilState?.docNo != null && ( */} - {/* )} */} - + {estimateRecoilState?.docNo !== null && (sessionState.storeId === 'T01' || sessionState.storeLvl === '1') && ( + + )}
)} {menuNumber === 6 && ( <>
- - @@ -313,11 +363,13 @@ export default function CanvasMenu(props) { )}
-
- {(menuNumber === 2 || menuNumber === 3 || menuNumber === 4) && } +
num === menuNumber) ? 'active' : ''}`}> + {[2, 3, 4].some((num) => num === menuNumber) && }
{/* 견적서(menuNumber=== 5) 상세화면인경우 문서다운로드 팝업 */} {estimatePopupOpen && } + {/* 견적서(menuNumber ===5)복사 팝업 */} + {estimateCopyPopupOpen && }
) } diff --git a/src/components/floor-plan/FloorPlan.jsx b/src/components/floor-plan/FloorPlan.jsx index 4baf3eb0..e2c53668 100644 --- a/src/components/floor-plan/FloorPlan.jsx +++ b/src/components/floor-plan/FloorPlan.jsx @@ -1,20 +1,20 @@ 'use client' -import { useEffect, useState } from 'react' -import { useRecoilState, useRecoilValue } from 'recoil' -import { settingModalFirstOptionsState, settingModalSecondOptionsState } from '@/store/settingAtom' +import { useContext, useEffect } from 'react' +//import { useRecoilState } from 'recoil' import CanvasMenu from '@/components/floor-plan/CanvasMenu' import { useCanvasMenu } from '@/hooks/common/useCanvasMenu' import { useCanvasSetting } from '@/hooks/option/useCanvasSetting' +import { usePopup } from '@/hooks/usePopup' +//import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider' +//import { correntObjectNoState } from '@/store/settingAtom' import '@/styles/contents.scss' export default function FloorPlan({ children }) { - const [settingModalFirstOptions, setSettingModalFirstOptions] = useRecoilState(settingModalFirstOptionsState) - const [settingModalSecondOptions, setSettingModalSecondOptions] = useRecoilState(settingModalSecondOptionsState) - const [objectNo, setObjectNo] = useState('test123240912001') // 이후 삭제 필요 - + //const { floorPlanState, setFloorPlanState } = useContext(FloorPlanContext) + //const [correntObjectNo, setCorrentObjectNo] = useRecoilState(correntObjectNoState) + const { closeAll } = usePopup() const { menuNumber, setMenuNumber } = useCanvasMenu() - const { fetchSettings } = useCanvasSetting() const modalProps = { @@ -23,8 +23,13 @@ export default function FloorPlan({ children }) { } useEffect(() => { + ///setCorrentObjectNo(floorPlanState.objectNo) + //console.log('FloorPlan objectNo ', floorPlanState.objectNo, correntObjectNo) fetchSettings() - }, [objectNo]) + return () => { + closeAll() + } + }, []) return ( <> diff --git a/src/components/floor-plan/MenuDepth01.jsx b/src/components/floor-plan/MenuDepth01.jsx index 351fdc97..5b1897b3 100644 --- a/src/components/floor-plan/MenuDepth01.jsx +++ b/src/components/floor-plan/MenuDepth01.jsx @@ -1,7 +1,7 @@ 'use client' import { useMessage } from '@/hooks/useMessage' -import { currentMenuState } from '@/store/canvasAtom' +import { canvasState, currentMenuState } from '@/store/canvasAtom' import { useRecoilState, useRecoilValue } from 'recoil' import { menuTypeState, subMenusState } from '@/store/menuAtom' import useMenu from '@/hooks/common/useMenu' @@ -9,6 +9,7 @@ import { useEffect } from 'react' export default function MenuDepth01() { const type = useRecoilValue(menuTypeState) + const canvas = useRecoilValue(canvasState) const { getMessage } = useMessage() const { handleMenu } = useMenu() const [currentMenu, setCurrentMenu] = useRecoilState(currentMenuState) @@ -24,6 +25,7 @@ export default function MenuDepth01() { useEffect(() => { handleMenu(type) + canvas.discardActiveObject() }, [currentMenu]) return (
diff --git a/src/components/floor-plan/modal/ImgLoad.jsx b/src/components/floor-plan/modal/ImgLoad.jsx new file mode 100644 index 00000000..c95d62a9 --- /dev/null +++ b/src/components/floor-plan/modal/ImgLoad.jsx @@ -0,0 +1,127 @@ +'use client' + +import { useContext, useEffect } from 'react' +import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider' + +import { useMessage } from '@/hooks/useMessage' +import { useRefFiles } from '@/hooks/common/useRefFiles' +import { usePlan } from '@/hooks/usePlan' + +import WithDraggable from '@/components/common/draggable/WithDraggable' + +export default function ImgLoad() { + const { + refImage, + queryRef, + setRefImage, + handleRefFile, + refFileMethod, + setRefFileMethod, + handleRefFileMethod, + mapPositionAddress, + setMapPositionAddress, + handleFileDelete, + handleMapImageDown, + } = useRefFiles() + const { currentCanvasPlan } = usePlan() + const { getMessage } = useMessage() + const { floorPlanState, setFloorPlanState } = useContext(FloorPlanContext) + + const handleModal = () => { + setFloorPlanState({ ...floorPlanState, refFileModalOpen: false }) + } + + useEffect(() => { + console.log('🚀 ~ ImgLoad ~ floorPlanState.refFileModalOpen:', floorPlanState.refFileModalOpen) + console.log('🚀 ~ ImgLoad ~ currentCanvasPlan:', currentCanvasPlan) + }, [floorPlanState.refFileModalOpen]) + + useEffect(() => { + const refFileMethod = currentCanvasPlan?.mapPositionAddress === null ? '1' : '2' + setRefFileMethod(refFileMethod) + }, [currentCanvasPlan]) + + return ( + +
+
+

{getMessage('common.input.file')}

+ {/* */} +
+
+
+ サイズ調整と回転 + +
+
+
+
+ handleRefFileMethod(e)} checked={refFileMethod === '1'} /> + +
+
+
+ + handleRefFile(e.target.files[0]) : () => {}} + /> +
+
+ {/* + */} + {currentCanvasPlan?.bgImageName === null ? ( + + ) : ( + + )} + {(refImage || currentCanvasPlan?.bgImageName) && } +
+
+
+
+
+ handleRefFileMethod(e)} checked={refFileMethod === '2'} /> + +
+
+ setMapPositionAddress(e.target.value)} + /> +
+ +
+ {mapPositionAddress && } + {/* */} +
+
+
+
+ +
+
+
+
+ ) +} diff --git a/src/components/floor-plan/modal/auxiliary/AuxiliaryCopy.jsx b/src/components/floor-plan/modal/auxiliary/AuxiliaryCopy.jsx deleted file mode 100644 index 32ffc78d..00000000 --- a/src/components/floor-plan/modal/auxiliary/AuxiliaryCopy.jsx +++ /dev/null @@ -1,84 +0,0 @@ -import { useMessage } from '@/hooks/useMessage' -import WithDraggable from '@/components/common/draggable/WithDraggable' -import { usePopup } from '@/hooks/usePopup' -import { useRecoilValue } from 'recoil' -import { contextPopupPositionState } from '@/store/popupAtom' -import { useState } from 'react' - -export default function AuxiliaryCopy(props) { - const contextPopupPosition = useRecoilValue(contextPopupPositionState) - const { id, pos = contextPopupPosition } = props - const { getMessage } = useMessage() - const { closePopup } = usePopup() - const [arrow1, setArrow1] = useState(null) - const [arrow2, setArrow2] = useState(null) - return ( - -
-
-

{getMessage('modal.auxiliary.copy')}

- -
-
-
{getMessage('modal.auxiliary.copy.info')}
-
-
-
-

{getMessage('length')}

-
-
- -
- mm -
- - -
-
-
-
- -
- mm -
- - -
-
-
-
-
-
- -
-
-
-
- ) -} diff --git a/src/components/floor-plan/modal/auxiliary/AuxiliaryDrawing.jsx b/src/components/floor-plan/modal/auxiliary/AuxiliaryDrawing.jsx index 84e7ab91..609ff975 100644 --- a/src/components/floor-plan/modal/auxiliary/AuxiliaryDrawing.jsx +++ b/src/components/floor-plan/modal/auxiliary/AuxiliaryDrawing.jsx @@ -49,6 +49,7 @@ export default function AuxiliaryDrawing({ id, pos = { x: 50, y: 230 } }) { handleFix, buttonAct, setButtonAct, + cutAuxiliary, } = useAuxiliaryDrawing(id) const outerLineProps = { @@ -151,6 +152,9 @@ export default function AuxiliaryDrawing({ id, pos = { x: 50, y: 230 } }) { + diff --git a/src/components/floor-plan/modal/auxiliary/AuxiliaryMove.jsx b/src/components/floor-plan/modal/auxiliary/AuxiliaryEdit.jsx similarity index 64% rename from src/components/floor-plan/modal/auxiliary/AuxiliaryMove.jsx rename to src/components/floor-plan/modal/auxiliary/AuxiliaryEdit.jsx index 733173b4..07fc9e46 100644 --- a/src/components/floor-plan/modal/auxiliary/AuxiliaryMove.jsx +++ b/src/components/floor-plan/modal/auxiliary/AuxiliaryEdit.jsx @@ -1,37 +1,66 @@ -'use client' - import { useMessage } from '@/hooks/useMessage' import WithDraggable from '@/components/common/draggable/WithDraggable' +import { usePopup } from '@/hooks/usePopup' import { useRecoilValue } from 'recoil' import { contextPopupPositionState } from '@/store/popupAtom' -import { usePopup } from '@/hooks/usePopup' import { useState } from 'react' +import { currentObjectState } from '@/store/canvasAtom' +import { useAuxiliaryDrawing } from '@/hooks/roofcover/useAuxiliaryDrawing' +import { useSwal } from '@/hooks/useSwal' -export default function AuxiliaryMove(props) { +export default function AuxiliaryEdit(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) - const { id, pos = contextPopupPosition } = props + const { id, pos = contextPopupPosition, type } = props const { getMessage } = useMessage() const { closePopup } = usePopup() + const { move, copy } = useAuxiliaryDrawing() + const [verticalSize, setVerticalSize] = useState('0') + const [horizonSize, setHorizonSize] = useState('0') const [arrow1, setArrow1] = useState(null) const [arrow2, setArrow2] = useState(null) + const currentObject = useRecoilValue(currentObjectState) + const { swalFire } = useSwal() + const handleSave = () => { + if (!horizonSize || !verticalSize || !arrow1 || !arrow2) { + swalFire({ title: '길이와 방향을 입력하세요.', type: 'alert' }) + return + } + if (type === 'copy') { + if (currentObject) { + copy( + currentObject, + arrow2 === '←' ? Number(horizonSize) * -1 : Number(horizonSize), + arrow1 === '↑' ? Number(verticalSize) * -1 : Number(verticalSize), + ) + } + } else { + move( + currentObject, + arrow2 === '←' ? Number(horizonSize) * -1 : Number(horizonSize), + arrow1 === '↑' ? Number(verticalSize) * -1 : Number(verticalSize), + ) + } + + closePopup(id) + } return (
-

{getMessage('modal.auxiliary.move')}

+

{getMessage(type === 'copy' ? 'modal.auxiliary.copy' : 'modal.auxiliary.move')}

-
{getMessage('modal.auxiliary.move.info')}
+
{getMessage(type === 'copy' ? 'modal.auxiliary.copy.info' : 'modal.auxiliary.move.info')}

{getMessage('length')}

- + setVerticalSize(e.target.value)} />
mm
@@ -53,7 +82,7 @@ export default function AuxiliaryMove(props) {
- + setHorizonSize(e.target.value)} />
mm
@@ -77,7 +106,9 @@ export default function AuxiliaryMove(props) {
- +
diff --git a/src/components/floor-plan/modal/auxiliary/AuxiliarySize.jsx b/src/components/floor-plan/modal/auxiliary/AuxiliarySize.jsx index 70245626..e984be57 100644 --- a/src/components/floor-plan/modal/auxiliary/AuxiliarySize.jsx +++ b/src/components/floor-plan/modal/auxiliary/AuxiliarySize.jsx @@ -3,12 +3,14 @@ import WithDraggable from '@/components/common/draggable/WithDraggable' import { usePopup } from '@/hooks/usePopup' import { useRecoilValue } from 'recoil' import { contextPopupPositionState } from '@/store/popupAtom' +import { useCanvas } from '@/hooks/useCanvas' export default function AuxiliarySize(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) const { id, pos = contextPopupPosition } = props const { getMessage } = useMessage() const { closePopup } = usePopup() + const { currentObject } = useCanvas() return (
@@ -26,7 +28,7 @@ export default function AuxiliarySize(props) {
- +
mm
@@ -45,14 +47,14 @@ export default function AuxiliarySize(props) {
- +
mm
{getMessage('length')}
- +
mm
diff --git a/src/components/floor-plan/modal/basic/BasicSetting.jsx b/src/components/floor-plan/modal/basic/BasicSetting.jsx index 6a7a1d0e..af5d9037 100644 --- a/src/components/floor-plan/modal/basic/BasicSetting.jsx +++ b/src/components/floor-plan/modal/basic/BasicSetting.jsx @@ -1,20 +1,47 @@ import { useMessage } from '@/hooks/useMessage' import WithDraggable from '@/components/common/draggable/WithDraggable' -import { useState } from 'react' -import Orientation from '@/components/floor-plan/modal/basic/step/Orientation' +import { useEffect, useRef, useState } from 'react' import Module from '@/components/floor-plan/modal/basic/step/Module' import PitchModule from '@/components/floor-plan/modal/basic/step/pitch/PitchModule' import PitchPlacement from '@/components/floor-plan/modal/basic/step/pitch/PitchPlacement' import Placement from '@/components/floor-plan/modal/basic/step/Placement' -import { useRecoilState } from 'recoil' +import { useRecoilValue } from 'recoil' import { canvasSettingState } from '@/store/canvasAtom' import { usePopup } from '@/hooks/usePopup' +import { Orientation } from '@/components/floor-plan/modal/basic/step/Orientation' +import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting' +import { useEvent } from '@/hooks/useEvent' export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) { const { getMessage } = useMessage() const { closePopup } = usePopup() const [tabNum, setTabNum] = useState(1) - const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState) + const canvasSetting = useRecoilValue(canvasSettingState) + const orientationRef = useRef(null) + const { initEvent } = useEvent() + // const { initEvent } = useContext(EventContext) + const { makeModuleInstArea, manualModuleSetup, autoModuleSetup } = useModuleBasicSetting() + const handleBtnNextStep = () => { + if (tabNum === 1) { + orientationRef.current.handleNextStep() + } + setTabNum(tabNum + 1) + } + + useEffect(() => { + makeModuleInstArea() //기붕 모듈설치면 생성 + + return () => { + initEvent() //모듈설치면 선택 이벤트 삭제 + } + }, []) + + const placementRef = { + isChidori: useRef('false'), + setupLocation: useRef('center'), + isMaxSetup: useRef('false'), + } + return (
@@ -32,10 +59,10 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
{getMessage('modal.module.basic.setting.module.placement')}
- {tabNum === 1 && } + {tabNum === 1 && } {/*배치면 초기설정 - 입력방법: 복시도 입력 || 실측값 입력*/} {canvasSetting.roofSizeSet && canvasSetting.roofSizeSet != 3 && tabNum === 2 && } - {canvasSetting.roofSizeSet && canvasSetting.roofSizeSet != 3 && tabNum === 3 && } + {canvasSetting.roofSizeSet && canvasSetting.roofSizeSet != 3 && tabNum === 3 && } {/*배치면 초기설정 - 입력방법: 육지붕*/} {canvasSetting.roofSizeSet && canvasSetting.roofSizeSet == 3 && tabNum === 2 && } @@ -49,14 +76,18 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) { )} {/*{tabNum !== 3 && }*/} {tabNum !== 3 && ( - )} {tabNum === 3 && ( <> - - + + )}
diff --git a/src/components/floor-plan/modal/basic/step/Module.jsx b/src/components/floor-plan/modal/basic/step/Module.jsx index f50bb408..e3e5c7bc 100644 --- a/src/components/floor-plan/modal/basic/step/Module.jsx +++ b/src/components/floor-plan/modal/basic/step/Module.jsx @@ -32,6 +32,16 @@ export default function Module({}) { }, ], } + const surfaceTypes = [ + { id: 1, name: 'Ⅱ', value: 'Ⅱ' }, + { id: 2, name: 'Ⅲ ∙ Ⅳ', value: 'Ⅲ ∙ Ⅳ' }, + ] + const fiftingHeights = Array.from({ length: 16 }).map((data, index) => { + return { id: index, name: index + 5, value: index + 5 } + }) + const windSpeeds = Array.from({ length: 7 }).map((data, index) => { + return { id: index, name: index * 2 + 30, value: index * 2 + 30 } + }) return ( <>
@@ -47,11 +57,13 @@ export default function Module({}) { - {moduleData.header.map((data) => ( - - ))} + {moduleData.header.map((header) => { + return ( + + ) + })} @@ -88,37 +100,50 @@ export default function Module({}) { -
-
- - - - -
+
-
{getMessage('modal.module.basic.setting.module.roof.material')}: スレーツ(4寸)
+
{getMessage('modal.module.basic.setting.module.stuff.info')}
-
{getMessage('modal.module.basic.setting.module.trestle.maker')}
+
{getMessage('modal.module.basic.setting.module.surface.type')}
-
- +
+
+ +
-
{getMessage('modal.module.basic.setting.module.construction.method')}
+
{getMessage('modal.module.basic.setting.module.fitting.height')}
-
- +
+
+ +
+ mm
-
{getMessage('modal.module.basic.setting.module.under.roof')}
+
{getMessage('modal.module.basic.setting.module.standard.wind.speed')}
-
- +
+
+ +
+ m/s +
+
+
+
+
{getMessage('modal.module.basic.setting.module.standard.snowfall.amount')}
+
+
+
+ +
+ mm
@@ -127,61 +152,93 @@ export default function Module({}) {
+
+ + + + +
-
-
- {getMessage('modal.module.basic.setting.module.cotton.classification')} -
- +
+
+
{getMessage('modal.module.basic.setting.module.roof.material')}:スレーツ(4寸)
+
+
+
{getMessage('modal.module.basic.setting.module.rafter.margin')}
+
+
+
+ +
+
+ 垂木の間隔 +
+ +
+
+
+
+
+
+
{getMessage('modal.module.basic.setting.module.trestle.maker')}
+
+
+ +
+
+
+
+
{getMessage('modal.module.basic.setting.module.construction.method')}
+
+
+ +
+
+
+
+
{getMessage('modal.module.basic.setting.module.under.roof')}
+
+
+ +
+
+
-
- {getMessage('modal.module.basic.setting.module.fitting.height')} -
- +
+
+ + + + +
-
-
- {getMessage('modal.module.basic.setting.module.standard.wind.speed')} -
- -
-
-
- {getMessage('modal.module.basic.setting.module.standard.snowfall.amount')} -
- +
+
+ + +
+
+ + +
-
-
-
- {getMessage('modal.module.basic.setting.module.setting.info1')} -
- {getMessage('modal.module.basic.setting.module.setting.info2')} -
+
+
+
+
+ {getMessage('modal.module.basic.setting.module.setting.info1')} +
+ {getMessage('modal.module.basic.setting.module.setting.info2')} +
-
-
- - - - - -
-
-
- - -
-
- - -
+ {/* 설정 오류시 노출 */} +
※ 施工方法が選択できません。 基準風速または基準積雪量を確認してください。
) diff --git a/src/components/floor-plan/modal/basic/step/Orientation.jsx b/src/components/floor-plan/modal/basic/step/Orientation.jsx index 98e10c0c..b080b1b1 100644 --- a/src/components/floor-plan/modal/basic/step/Orientation.jsx +++ b/src/components/floor-plan/modal/basic/step/Orientation.jsx @@ -1,22 +1,21 @@ -import { useState } from 'react' +import { forwardRef, useImperativeHandle, useState } from 'react' import { useMessage } from '@/hooks/useMessage' +import { useOrientation } from '@/hooks/module/useOrientation' +import { getDegreeInOrientation } from '@/util/canvas-util' -export default function Orientation({ setTabNum }) { +export const Orientation = forwardRef(({ tabNum }, ref) => { const { getMessage } = useMessage() - const [compasDeg, setCompasDeg] = useState(0) + + const { nextStep, compasDeg, setCompasDeg } = useOrientation() + const [hasAnglePassivity, setHasAnglePassivity] = useState(false) - const getDegree = (degree) => { - if (degree % 15 === 0) return degree + useImperativeHandle(ref, () => ({ + handleNextStep, + })) - let value = Math.floor(degree / 15) - const remain = ((degree / 15) % 1).toFixed(5) - - if (remain > 0.4) { - value++ - } - - return value * 15 + const handleNextStep = () => { + nextStep() } return ( @@ -31,7 +30,7 @@ export default function Orientation({ setTabNum }) { {Array.from({ length: 180 / 15 }).map((dot, index) => (
setCompasDeg(15 * (12 + index))} > {index === 0 && 180°} @@ -39,13 +38,17 @@ export default function Orientation({ setTabNum }) {
))} {Array.from({ length: 180 / 15 }).map((dot, index) => ( -
setCompasDeg(15 * index)}> +
setCompasDeg(15 * index)} + > {index === 0 && } {index === 6 && 90°}
))}
-
+
@@ -62,7 +65,11 @@ export default function Orientation({ setTabNum }) { className="input-origin block" value={compasDeg} readOnly={hasAnglePassivity} - onChange={(e) => setCompasDeg(e.target.value !== '' ? Number.parseInt(e.target.value) : 0)} + onChange={(e) => + setCompasDeg( + e.target.value !== '' && parseInt(e.target.value) <= 360 && parseInt(e.target.value) >= 0 ? Number.parseInt(e.target.value) : 0, + ) + } />
° @@ -72,4 +79,4 @@ export default function Orientation({ setTabNum }) {
) -} +}) diff --git a/src/components/floor-plan/modal/basic/step/Placement.jsx b/src/components/floor-plan/modal/basic/step/Placement.jsx index 4771ab2a..13c48a08 100644 --- a/src/components/floor-plan/modal/basic/step/Placement.jsx +++ b/src/components/floor-plan/modal/basic/step/Placement.jsx @@ -1,7 +1,12 @@ import { useMessage } from '@/hooks/useMessage' +import { forwardRef, useState } from 'react' -export default function Placement() { +const Placement = forwardRef((props, refs) => { const { getMessage } = useMessage() + const [isChidori, setIsChidori] = useState('false') + const [setupLocation, setSetupLocation] = useState('center') + const [isMaxSetup, setIsMaxSetup] = useState('false') + const moduleData = { header: [ { type: 'check', name: '', prop: 'check', width: 70 }, @@ -24,6 +29,29 @@ export default function Placement() { }, ], } + + const handleChangeChidori = (e) => { + setIsChidori(e.target.value) + refs.isChidori.current = e.target.value + } + + const handleSetupLocation = (e) => { + setSetupLocation(e.target.value) + refs.setupLocation.current = e.target.value + } + + const handleMaxSetup = (e) => { + console.log(e.target.checked) + + if (e.target.checked) { + setIsMaxSetup('true') + refs.isMaxSetup.current = 'true' + } else { + setIsMaxSetup('false') + refs.isMaxSetup.current = 'false' + } + } + return ( <>
@@ -96,12 +124,20 @@ export default function Placement() {
- +
- - + +
@@ -111,15 +147,36 @@ export default function Placement() {
- +
- +
- +
@@ -128,7 +185,7 @@ export default function Placement() {
- +
@@ -137,4 +194,6 @@ export default function Placement() {
) -} +}) + +export default Placement diff --git a/src/components/floor-plan/modal/distance/Distance.jsx b/src/components/floor-plan/modal/distance/Distance.jsx index 745ca4f2..ac65c1e5 100644 --- a/src/components/floor-plan/modal/distance/Distance.jsx +++ b/src/components/floor-plan/modal/distance/Distance.jsx @@ -63,7 +63,9 @@ export default function Distance(props) {
- +
diff --git a/src/components/floor-plan/modal/flowDirection/FlowDirectionSetting.jsx b/src/components/floor-plan/modal/flowDirection/FlowDirectionSetting.jsx index effb4717..da49b07c 100644 --- a/src/components/floor-plan/modal/flowDirection/FlowDirectionSetting.jsx +++ b/src/components/floor-plan/modal/flowDirection/FlowDirectionSetting.jsx @@ -6,17 +6,27 @@ import { contextPopupPositionState } from '@/store/popupAtom' import { useMessage } from '@/hooks/useMessage' import { usePopup } from '@/hooks/usePopup' import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch' +import { FLOW_DIRECTION_TYPE, useFlowDirectionSetting } from '@/hooks/contextpopup/useFlowDirectionSetting' +import { canvasState } from '@/store/canvasAtom' export default function FlowDirectionSetting(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) const { id, pos = contextPopupPosition, target } = props + const canvas = useRecoilValue(canvasState) const { getMessage } = useMessage() - const { closePopup } = usePopup() - const [compasDeg, setCompasDeg] = useState(360) + + useEffect(() => { + return () => { + canvas?.discardActiveObject() + } + }, []) + + const [compasDeg, setCompasDeg] = useState(null) const [flowDirection, setFlowDirection] = useState(target.direction) - const { changeSurfaceFlowDirection } = useSurfaceShapeBatch() + const { closePopup } = usePopup() + const { changeSurfaceFlowDirection, type, setType } = useFlowDirectionSetting(id) const orientations = [ - // { name: `${getMessage('commons.none')}`, value: 0 }, + { name: `${getMessage('commons.none')}`, value: 0 }, { name: `${getMessage('commons.south')}`, value: 360 }, { name: `${getMessage('commons.south')}${getMessage('commons.east')}`, value: 315 }, { name: `${getMessage('commons.south')}${getMessage('commons.west')}`, value: 45 }, @@ -27,41 +37,7 @@ export default function FlowDirectionSetting(props) { { name: `${getMessage('commons.north')}`, value: 180 }, ] const [selectedOrientation, setSelectedOrientation] = useState(orientations[0]) - const [type, setType] = useState('0') - useEffect(() => { - if (target?.angle === 0) { - setCompasDeg(360) - } else { - setCompasDeg(target?.angle ?? 360) - } - }, []) - useEffect(() => { - if (type === '0') { - setCompasDeg(selectedOrientation.value) - } - }, [selectedOrientation]) - useEffect(() => { - if (type === '1') { - if ([15, 345, 360].includes(compasDeg)) { - setSelectedOrientation(orientations[0]) - } else if ([30, 45, 60].includes(compasDeg)) { - setSelectedOrientation(orientations[2]) - } else if ([75, 90, 105].includes(compasDeg)) { - setSelectedOrientation(orientations[4]) - } else if ([120, 135, 150].includes(compasDeg)) { - setSelectedOrientation(orientations[6]) - } else if ([165, 180, 195].includes(compasDeg)) { - setSelectedOrientation(orientations[7]) - } else if ([210, 225, 240].includes(compasDeg)) { - setSelectedOrientation(orientations[5]) - } else if ([255, 270, 285].includes(compasDeg)) { - setSelectedOrientation(orientations[3]) - } else if ([300, 315, 330].includes(compasDeg)) { - setSelectedOrientation(orientations[1]) - } - } - }, [compasDeg]) return (
@@ -94,16 +70,43 @@ export default function FlowDirectionSetting(props) {
{getMessage('modal.shape.flow.direction.setting.orientation.setting.info')}
- setType(e.target.value)} /> + { + setCompasDeg(0) + setType(FLOW_DIRECTION_TYPE.EIGHT_AZIMUTH) + }} + />
- setSelectedOrientation(e)} /> + { + setType(FLOW_DIRECTION_TYPE.EIGHT_AZIMUTH) + setSelectedOrientation(e) + setCompasDeg(e.value) + }} + />
- setType(e.target.value)} /> + { + setType(FLOW_DIRECTION_TYPE.TWENTY_FOUR_AZIMUTH) + }} + />
@@ -114,18 +117,27 @@ export default function FlowDirectionSetting(props) {
setCompasDeg(15 * (12 + index))} + onClick={() => { + setType(FLOW_DIRECTION_TYPE.TWENTY_FOUR_AZIMUTH) + setCompasDeg(15 * (12 + index)) + }} >
))} {Array.from({ length: 180 / 15 - 1 }).map((dot, index) => (
setCompasDeg(15 * (index + 1))} + onClick={() => { + setType(FLOW_DIRECTION_TYPE.TWENTY_FOUR_AZIMUTH) + setCompasDeg(15 * (index + 1)) + }} >
))}
-
+
@@ -133,7 +145,7 @@ export default function FlowDirectionSetting(props) {
-
diff --git a/src/components/floor-plan/modal/image/ImageSizeSetting.jsx b/src/components/floor-plan/modal/image/ImageSizeSetting.jsx deleted file mode 100644 index 309df494..00000000 --- a/src/components/floor-plan/modal/image/ImageSizeSetting.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import WithDraggable from '@/components/common/draggable/WithDraggable' -import { useState } from 'react' -import { usePopup } from '@/hooks/usePopup' -import { useRecoilValue } from 'recoil' -import { contextPopupPositionState } from '@/store/popupAtom' -import { useMessage } from '@/hooks/useMessage' - -export default function ImageSizeSetting(props) { - const contextPopupPosition = useRecoilValue(contextPopupPositionState) - const { id, pos = contextPopupPosition, size, setSize } = props - const [sizeValue, setSizeValue] = useState(100) - const { getMessage } = useMessage() - const { closePopup } = usePopup() - - return ( - -
-
-

{getMessage('modal.image.size.setting')}

- -
-
-
- setSizeValue(e.target.value)} - /> - -
-
-
-
- ) -} diff --git a/src/components/floor-plan/modal/lineProperty/LinePropertySetting.jsx b/src/components/floor-plan/modal/lineProperty/LinePropertySetting.jsx index 5b4075f0..13a00b06 100644 --- a/src/components/floor-plan/modal/lineProperty/LinePropertySetting.jsx +++ b/src/components/floor-plan/modal/lineProperty/LinePropertySetting.jsx @@ -3,51 +3,58 @@ import { useRecoilValue } from 'recoil' import { contextPopupPositionState } from '@/store/popupAtom' import { useMessage } from '@/hooks/useMessage' import { usePopup } from '@/hooks/usePopup' -import { useState, useEffect } from 'react' +import { useEffect, useState } from 'react' import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch' import { useEvent } from '@/hooks/useEvent' +import { LINE_TYPE } from '@/common/common' export default function LinePropertySetting(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) const { id, pos = contextPopupPosition, target } = props const { getMessage } = useMessage() const { closePopup } = usePopup() - const { changeSurfaceLinePropertyEvent, changeSurfaceLineProperty } = useSurfaceShapeBatch() + const { changeSurfaceLinePropertyEvent, changeSurfaceLineProperty, changeSurfaceLinePropertyReset } = useSurfaceShapeBatch() const { initEvent } = useEvent() + // const { initEvent } = useContext(EventContext) const properties = [ - { name: getMessage('eaves.line'), value: 'eaves' }, - { name: getMessage('ridge'), value: 'ridge' }, - { name: getMessage('oneside.flow.ridge'), value: 'onesideFlowRidge' }, - { name: getMessage('gable'), value: 'gable' }, - { name: getMessage('gable.left'), value: 'gableLeft' }, - { name: getMessage('gable.right'), value: 'gableRight' }, - { name: getMessage('yosemune'), value: 'yosemune' }, - { name: getMessage('valley'), value: 'valley' }, - { name: getMessage('l.abandon.valley'), value: 'lAbandonValley' }, - { name: getMessage('mansard'), value: 'mansard' }, - { name: getMessage('wall.merge'), value: 'wallCollection' }, - { name: getMessage('wall.merge.type'), value: 'wallCollectionType' }, - { name: getMessage('wall.merge.flow'), value: 'wallCollectionFlow' }, - { name: getMessage('wall.merge.flow.left'), value: 'wallCollectionFlowLeft' }, - { name: getMessage('wall.merge.flow.right'), value: 'wallCollectionFlowRight' }, - { name: getMessage('no.setting'), value: 'noSetting' }, + { name: getMessage('eaves.line'), value: LINE_TYPE.WALLLINE.EAVES }, + { name: getMessage('ridge'), value: LINE_TYPE.SUBLINE.RIDGE }, + { name: getMessage('oneside.flow.ridge'), value: LINE_TYPE.SUBLINE.ONESIDE_FLOW_RIDGE }, + { name: getMessage('gable'), value: LINE_TYPE.WALLLINE.GABLE }, + { name: getMessage('gable.left'), value: LINE_TYPE.WALLLINE.GABLE_LEFT }, + { name: getMessage('gable.right'), value: LINE_TYPE.WALLLINE.GABLE_RIGHT }, + { name: getMessage('yosemune'), value: LINE_TYPE.SUBLINE.YOSEMUNE }, + { name: getMessage('valley'), value: LINE_TYPE.SUBLINE.YOSEMUNE }, + { name: getMessage('l.abandon.valley'), value: LINE_TYPE.SUBLINE.L_ABANDON_VALLEY }, + { name: getMessage('mansard'), value: LINE_TYPE.SUBLINE.MANSARD }, + { name: getMessage('wall.merge'), value: LINE_TYPE.SUBLINE.WALL_COLLECTION }, + { name: getMessage('wall.merge.type'), value: LINE_TYPE.SUBLINE.WALL_COLLECTION_TYPE }, + { name: getMessage('wall.merge.flow'), value: LINE_TYPE.SUBLINE.WALL_COLLECTION_FLOW }, + { name: getMessage('wall.merge.flow.left'), value: LINE_TYPE.SUBLINE.WALL_COLLECTION_FLOW_LEFT }, + { name: getMessage('wall.merge.flow.right'), value: LINE_TYPE.SUBLINE.WALL_COLLECTION_FLOW_RIGHT }, + { name: getMessage('no.setting'), value: LINE_TYPE.WALLLINE.DEFAULT }, ] const [selectedProperty, setSelectedProperty] = useState(null) useEffect(() => { - changeSurfaceLinePropertyEvent(target) + changeSurfaceLinePropertyEvent() return () => { initEvent() } }, []) + const handleClosePopup = () => { + closePopup(id) + changeSurfaceLinePropertyReset(target) + } + return (

{getMessage('contextmenu.line.property.edit')}

-
@@ -79,7 +86,7 @@ export default function LinePropertySetting(props) {
-
diff --git a/src/components/floor-plan/modal/object/SizeSetting.jsx b/src/components/floor-plan/modal/object/SizeSetting.jsx index cadfd461..bddc9f07 100644 --- a/src/components/floor-plan/modal/object/SizeSetting.jsx +++ b/src/components/floor-plan/modal/object/SizeSetting.jsx @@ -5,11 +5,12 @@ import { useMessage } from '@/hooks/useMessage' import WithDraggable from '@/components/common/draggable/WithDraggable' import { usePopup } from '@/hooks/usePopup' import { contextPopupPositionState } from '@/store/popupAtom' -import { useRef, useState, useEffect } from 'react' +import { useRef, useState, useEffect, useContext } from 'react' import { useObjectBatch } from '@/hooks/object/useObjectBatch' import { useEvent } from '@/hooks/useEvent' import { BATCH_TYPE, POLYGON_TYPE } from '@/common/common' import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch' +import { EventContext } from '@/app/floor-plan/EventProvider' export default function SizeSetting(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) @@ -22,11 +23,12 @@ export default function SizeSetting(props) { const widthRef = useRef(null) const heightRef = useRef(null) - const { initEvent } = useEvent() + // const { initEvent } = useEvent() + // const { initEvent } = useContext(EventContext) - useEffect(() => { - initEvent() - }, []) + // useEffect(() => { + // initEvent() + // }, []) const handleReSizeObject = () => { const width = widthRef.current.value diff --git a/src/components/floor-plan/modal/object/type/PentagonDormer.jsx b/src/components/floor-plan/modal/object/type/PentagonDormer.jsx index c67a08fc..0bca7527 100644 --- a/src/components/floor-plan/modal/object/type/PentagonDormer.jsx +++ b/src/components/floor-plan/modal/object/type/PentagonDormer.jsx @@ -82,7 +82,7 @@ const PentagonDormer = forwardRef((props, refs) => {
-
方向の選択
+
{getMessage('modal.object.setting.direction.select')}
{getMessage('commons.north')} diff --git a/src/components/floor-plan/modal/object/type/TriangleDormer.jsx b/src/components/floor-plan/modal/object/type/TriangleDormer.jsx index 34a87e44..229043c8 100644 --- a/src/components/floor-plan/modal/object/type/TriangleDormer.jsx +++ b/src/components/floor-plan/modal/object/type/TriangleDormer.jsx @@ -60,7 +60,7 @@ const TriangleDormer = forwardRef((props, refs) => {
-
方向の選択
+
{getMessage('modal.object.setting.direction.select')}
{getMessage('commons.north')} diff --git a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx index c8dbdbca..26d6f1b9 100644 --- a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx +++ b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx @@ -8,13 +8,10 @@ import { useMessage } from '@/hooks/useMessage' import { useAxios } from '@/hooks/useAxios' import { useSwal } from '@/hooks/useSwal' import { usePopup } from '@/hooks/usePopup' -import useRefFiles from '@/hooks/common/useRefFiles' -import { usePlan } from '@/hooks/usePlan' import SizeGuide from '@/components/floor-plan/modal/placementShape/SizeGuide' import MaterialGuide from '@/components/floor-plan/modal/placementShape/MaterialGuide' import WithDraggable from '@/components/common/draggable/WithDraggable' -import { SessionContext } from '@/app/SessionProvider' export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, setShowPlaceShapeModal }) { const [objectNo, setObjectNo] = useState('test123241008001') // 후에 삭제 필요 @@ -24,20 +21,6 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, set const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState) const { closePopup } = usePopup() const [basicSetting, setBasicSettings] = useRecoilState(basicSettingState) - const { - refImage, - queryRef, - setRefImage, - handleRefFile, - refFileMethod, - setRefFileMethod, - handleRefFileMethod, - mapPositionAddress, - setMapPositionAddress, - handleFileDelete, - handleMapImageDown, - } = useRefFiles() - const { currentCanvasPlan } = usePlan() const { getMessage } = useMessage() const { get, post } = useAxios() @@ -503,90 +486,6 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, set
-
- - -
- {data.name} - + {header.name} +
{getMessage('common.input.file')} -
-
- handleRefFileMethod(e)} - checked={refFileMethod === '1'} - /> - -
-
- handleRefFileMethod(e)} - checked={refFileMethod === '2'} - /> - -
-
- - {/* 파일 불러오기 */} - {refFileMethod === '1' && ( -
-
- - handleRefFile(e.target.files[0])} /> -
-
- {currentCanvasPlan?.bgImageName === null ? ( - - ) : ( - - )} - {(refImage || currentCanvasPlan?.bgImageName) && } -
-
- )} - - {/* 주소 불러오기 */} - {refFileMethod === '2' && ( -
- setMapPositionAddress(e.target.value)} - /> -
- -
- {mapPositionAddress && } - {/* */} -
- )} - {/*
-
- - handleRefFile(e.target.files[0])} /> -
-
- - {refImage && } -
-
*/} -
diff --git a/src/components/floor-plan/modal/setting01/FirstOption.jsx b/src/components/floor-plan/modal/setting01/FirstOption.jsx index cb3253b3..1f70f21f 100644 --- a/src/components/floor-plan/modal/setting01/FirstOption.jsx +++ b/src/components/floor-plan/modal/setting01/FirstOption.jsx @@ -1,19 +1,46 @@ import React, { useEffect, useState } from 'react' import { useCanvasSetting } from '@/hooks/option/useCanvasSetting' import { useMessage } from '@/hooks/useMessage' +import { POLYGON_TYPE } from '@/common/common' +import { setSurfaceShapePattern } from '@/util/canvas-util' export default function FirstOption() { - const [objectNo, setObjectNo] = useState('test123240912001') // 이후 삭제 필요 - const { settingModalFirstOptions, setSettingModalFirstOptions } = useCanvasSetting() - const { settingModalSecondOptions, setSettingModalSecondOptions } = useCanvasSetting() const { getMessage } = useMessage() - - const { fetchSettings, frontSettings, onClickOption } = useCanvasSetting() + const { canvas, settingModalFirstOptions, setSettingModalFirstOptions } = useCanvasSetting() + const { option1, option2, dimensionDisplay } = settingModalFirstOptions // 데이터를 최초 한 번만 조회 useEffect(() => { console.log('FirstOption useEffect 실행') - }, [objectNo]) + }, []) + + const onClickOption = async (item) => { + //치수 표시(단 건 선택) + if (item.column === 'corridorDimension' || item.column === 'realDimension' || item.column === 'noneDimension') { + const options = settingModalFirstOptions?.dimensionDisplay.map((option) => { + option.selected = option.id === item.id + return option + }) + + //화면 표시(단 건 선택) + } else if (item.column === 'onlyBorder' || item.column === 'lineHatch' || item.column === 'allPainted') { + const options2 = settingModalFirstOptions?.option2.map((option2) => { + option2.selected = option2.id === item.id + return option2 + }) + + const polygons = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) + + polygons.forEach((polygon) => { + setSurfaceShapePattern(polygon, item.column) + }) + //디스플레이 설정 표시(단 건 선택) + } else { + item.selected = !item.selected + } + + setSettingModalFirstOptions({ ...settingModalFirstOptions, option1, option2, dimensionDisplay, fontFlag: true }) + } return ( <> diff --git a/src/components/floor-plan/modal/setting01/SecondOption.jsx b/src/components/floor-plan/modal/setting01/SecondOption.jsx index 385c7bef..b5e9d98d 100644 --- a/src/components/floor-plan/modal/setting01/SecondOption.jsx +++ b/src/components/floor-plan/modal/setting01/SecondOption.jsx @@ -1,4 +1,3 @@ -import { useRecoilValue } from 'recoil' import { useMessage } from '@/hooks/useMessage' import React, { useEffect, useState } from 'react' import DimensionLineSetting from '@/components/floor-plan/modal/setting01/dimensionLine/DimensionLineSetting' @@ -6,80 +5,44 @@ import { usePopup } from '@/hooks/usePopup' import { v4 as uuidv4 } from 'uuid' import FontSetting from '@/components/common/font/FontSetting' import PlanSizeSetting from '@/components/floor-plan/modal/setting01/planSize/PlanSizeSetting' -import { dimensionLineSettingsState } from '@/store/commonUtilsAtom' import { useCanvasSetting } from '@/hooks/option/useCanvasSetting' +import { useRecoilState, useRecoilValue } from 'recoil' +import { fontSelector, globalFontAtom } from '@/store/fontAtom' export default function SecondOption() { const { getMessage } = useMessage() - const { addPopup, closePopup, closePopups } = usePopup() + const { addPopup, closePopup } = usePopup() const [showFontSettingModal, setShowFontSettingModal] = useState(false) const [showDimensionLineSettingModal, setShowDimensionLineSettingModal] = useState(false) const [showPlanSizeSettingModal, setShowPlanSizeSettingModal] = useState(false) - const dimensionSettings = useRecoilValue(dimensionLineSettingsState) + const [globalFont, setGlobalFont] = useRecoilState(globalFontAtom) + const commonFont = useRecoilValue(fontSelector('commonText')) + const flowFont = useRecoilValue(fontSelector('flowText')) + const lengthFont = useRecoilValue(fontSelector('lengthText')) + const circuitNumberTextFont = useRecoilValue(fontSelector('circuitNumberText')) + const [dimensionId, setDimensionId] = useState(uuidv4()) + const [fontId, setFontId] = useState(uuidv4()) + const [planSizeId, setPlanSizeId] = useState(uuidv4()) - const [objectNo, setObjectNo] = useState('test123240912001') // 이후 삭제 필요 - const { settingModalFirstOptions, setSettingModalFirstOptions } = useCanvasSetting() - const { settingModalSecondOptions, setSettingModalSecondOptions } = useCanvasSetting() - const { adsorptionPointMode, setAdsorptionPointMode } = useCanvasSetting() - const { fetchSettings, frontSettings, onClickOption } = useCanvasSetting() + const { + fetchSettings, + planSizeSettingMode, + setPlanSizeSettingMode, + settingModalSecondOptions, + setSettingModalSecondOptions, + adsorptionPointMode, + setAdsorptionPointMode, + setAdsorptionRange, + } = useCanvasSetting() + const { option3, option4 } = settingModalSecondOptions // 데이터를 최초 한 번만 조회 useEffect(() => { console.log('SecondOption useEffect 실행') - //fetchSettings() - }, [objectNo]) - - let dimensionId = null - let fontId = null - let planSizeId = null - const [pixel, setPixel] = useState(dimensionSettings.pixel) - const [color, setColor] = useState(dimensionSettings.color) - const [font, setFont] = useState(null) - const [fontSize, setFontSize] = useState(dimensionSettings.fontSize) - const [fontColor, setFontColor] = useState(dimensionSettings.fontColor) - - useEffect(() => { - dimensionId = uuidv4() - fontId = uuidv4() - planSizeId = uuidv4() }, []) - const dimensionProps = { - color, - setColor, - pixel, - setPixel, - font, - setFont, - fontSize, - setFontSize, - fontColor, - setFontColor, - id: dimensionId, - isShow: showDimensionLineSettingModal, - setIsShow: setShowDimensionLineSettingModal, - } - - const [horizon, setHorizon] = useState(1600) - const [vertical, setVertical] = useState(1600) - - const fontProps = { - id: fontId, - pos: { x: 745, y: 180 }, - setIsShow: setShowFontSettingModal, - isConfig: true, - } - const planSizeProps = { - id: planSizeId, - horizon, - setHorizon, - vertical, - setVertical, - setIsShow: setShowPlanSizeSettingModal, - pos: { x: 1025, y: 180 }, - } - const handlePopup = (type) => { + setShowDimensionLineSettingModal(false) setShowPlanSizeSettingModal(false) setShowFontSettingModal(false) @@ -89,6 +52,7 @@ export default function SecondOption() { setShowFontSettingModal(true) setShowDimensionLineSettingModal(false) fontProps.type = 'commonText' + fontProps.font = commonFont fontProps.id = fontId + 1 addPopup(fontId + 1, 2, , true) break @@ -99,6 +63,7 @@ export default function SecondOption() { setShowFontSettingModal(true) setShowDimensionLineSettingModal(false) fontProps.type = 'flowText' + fontProps.font = flowFont fontProps.id = fontId + 2 addPopup(fontId + 2, 2, , true) break @@ -107,9 +72,9 @@ export default function SecondOption() { case 'font3': { //치수 글꼴변경 setShowFontSettingModal(true) - setShowDimensionLineSettingModal(false) fontProps.type = 'lengthText' + fontProps.font = lengthFont fontProps.id = fontId + 3 addPopup(fontId + 3, 2, , true) break @@ -120,6 +85,7 @@ export default function SecondOption() { setShowFontSettingModal(true) setShowDimensionLineSettingModal(false) fontProps.type = 'circuitNumberText' + fontProps.font = circuitNumberTextFont fontProps.id = fontId addPopup(fontId, 2, , true) break @@ -129,6 +95,12 @@ export default function SecondOption() { //치수선 설정 if (!showDimensionLineSettingModal) { setShowDimensionLineSettingModal(true) + fontProps.font = { + fontFamily: '', + fontWeight: '', + fontSize: '', + fontColor: '', + } addPopup(dimensionId, 2, , true) } else { setShowDimensionLineSettingModal(false) @@ -139,14 +111,84 @@ export default function SecondOption() { case 'planSize': { //도면크기 설정 - setShowPlanSizeSettingModal(true) - setShowDimensionLineSettingModal(false) - addPopup(planSizeId, 2, , true) + if (!showPlanSizeSettingModal) { + fetchSettings() //화면 오픈 시 데이터 조회 + setShowPlanSizeSettingModal(true) + addPopup(planSizeId, 2, , true) + } else { + setShowPlanSizeSettingModal(false) + closePopup(planSizeId, true) + } break } } } + const handleFontSave = (font) => { + setGlobalFont((prev) => { + return { + ...prev, + [fontProps.type]: { + // fontFamily: font.fontFamily.value, + // fontWeight: font.fontWeight.value, + // fontSize: font.fontSize.value, + // fontColor: font.fontColor.value, + fontFamily: font.fontFamily, + fontWeight: font.fontWeight, + fontSize: font.fontSize, + fontColor: font.fontColor, + }, + fontFlag: true, + } + }) + } + + const fontProps = { + id: fontId, + pos: { x: 745, y: 180 }, + setIsShow: setShowFontSettingModal, + onSave: handleFontSave, + isConfig: true, + } + + const dimensionProps = { + id: dimensionId, + isShow: showDimensionLineSettingModal, + setIsShow: setShowDimensionLineSettingModal, + } + + const planSizeProps = { + id: planSizeId, + horizon: planSizeSettingMode.originHorizon, + vertical: planSizeSettingMode.originVertical, + isShow: showPlanSizeSettingModal, + setIsShow: setShowPlanSizeSettingModal, + pos: { x: 1025, y: 180 }, + } + + const onClickOption = async (item) => { + //흡착범위 설정(단 건 선택) + if ( + item.column === 'adsorpRangeSmall' || + item.column === 'adsorpRangeSmallSemi' || + item.column === 'adsorpRangeMedium' || + item.column === 'adsorpRangeLarge' + ) { + // option4에서 한 개만 선택 가능하도록 처리 + //const updatedOption4 = option4.map((option) => (option.id === item.id ? { ...option, selected: true } : { ...option, selected: false })) + const options = settingModalSecondOptions?.option4.map((option4) => { + option4.selected = option4.id === item.id + return option4 + }) + + setSettingModalSecondOptions({ ...settingModalSecondOptions, option3, option4, fontFlag: true }) + } else if (item === 'adsorpPoint') { + setAdsorptionPointMode({ ...adsorptionPointMode, adsorptionPoint: !adsorptionPointMode.adsorptionPoint, fontFlag: true }) + } + //setAdsorptionRange(item.range) //사용여부 확인 필요 + setAdsorptionRange(50) + } + return ( <>
@@ -181,12 +223,12 @@ export default function SecondOption() {
diff --git a/src/components/floor-plan/modal/setting01/dimensionLine/DimensionLineSetting.jsx b/src/components/floor-plan/modal/setting01/dimensionLine/DimensionLineSetting.jsx index 1aa39e74..63581d5d 100644 --- a/src/components/floor-plan/modal/setting01/dimensionLine/DimensionLineSetting.jsx +++ b/src/components/floor-plan/modal/setting01/dimensionLine/DimensionLineSetting.jsx @@ -8,68 +8,110 @@ import QSelectBox from '@/components/common/select/QSelectBox' import { useMessage } from '@/hooks/useMessage' import { dimensionLineSettingsState } from '@/store/commonUtilsAtom' import { useRecoilState } from 'recoil' +import { globalFontAtom } from '@/store/fontAtom' +import { useCanvasSetting } from '@/hooks/option/useCanvasSetting' + +const fonts = [ + { id: 1, name: 'MS PGothic', value: 'MS PGothic' }, + { id: 2, name: '@Yu Gothic', value: '@Yu Gothic' }, + { id: 3, name: 'Yu Gothic', value: 'Yu Gothic' }, + { id: 4, name: '@Yu Gothic UI', value: '@Yu Gothic UI' }, + { id: 5, name: 'Yu Gothic UI', value: 'Yu Gothic UI' }, + //3, +] + +const fontSizes = [ + ...Array.from({ length: 4 }).map((_, index) => { + return { id: index + 8, name: index + 8, value: index + 8 } + }), + ...Array.from({ length: 9 }).map((_, index) => { + return { id: (index + 6) * 2, name: (index + 6) * 2, value: (index + 6) * 2 } + }), + { id: 36, name: 36, value: 36 }, + { id: 48, name: 48, value: 48 }, + { id: 72, name: 72, value: 72 }, +] -/* - color: 치수선 색 - fontColor: 글꼴 색 - fontSize: 치수선 치수 색 - pixel: 치수선 두깨 -*/ export default function DimensionLineSetting(props) { - const { - color, - setColor, - font, - setFont, - fontColor, - setFontColor, - fontSize, - setFontSize, - pixel, - setPixel, - isShow, - setIsShow, - id, - pos = { x: 985, y: 180 }, - } = props + const { isShow, setIsShow, id, pos = { x: 985, y: 180 } } = props const { addPopup, closePopup, closePopups } = usePopup() const pixels = Array.from({ length: 5 }).map((_, index) => { - return { name: index + 1, value: index + 1 } + return { id: index, name: index + 1, value: index + 1 } }) - const [originColor, setOriginColor] = useState(color) - const [originFont, setOriginFont] = useState(font) - const [originFontColor, setOriginFontColor] = useState(fontColor) - const [originFontSize, setOriginFontSize] = useState(fontSize) - const [originPixel, setOriginPixel] = useState(pixel) + //const [dimensionLineSettings, setDimensionLineSettings] = useRecoilState(dimensionLineSettingsState) + //const [originPixel, setOriginPixel] = useState(dimensionLineSettings.pixel) + //const [originColor, setOriginColor] = useState(dimensionLineSettings.color) + //const [globalFont, setGlobalFont] = useRecoilState(globalFontAtom) + const [fontModalId, setFontModalId] = useState(uuidv4()) const [colorModalId, setColorModalId] = useState(uuidv4()) - const [showColorPickerModal, setShowColorPickerModal] = useState(false) const [showFontModal, setShowFontModal] = useState(false) const { getMessage } = useMessage() - const [dimensionLineSettings, setDimensionLineSettings] = useRecoilState(dimensionLineSettingsState) + + const { globalFont, setGlobalFont, dimensionLineSettings, setDimensionLineSettings } = useCanvasSetting() + + const [originFont, setOriginFont] = useState(globalFont.dimensionLineText.fontFamily) + const [originFontWeight, setOriginFontWeight] = useState(globalFont.dimensionLineText.fontWeight) + const [originFontSize, setOriginFontSize] = useState(globalFont.dimensionLineText.fontSize) + const [originFontColor, setOriginFontColor] = useState(globalFont.dimensionLineText.fontColor) + + const [originPixel, setOriginPixel] = useState(dimensionLineSettings.pixel) + const [originColor, setOriginColor] = useState(dimensionLineSettings.color) + + const fontOptions = [ + { id: 'normal', name: getMessage('font.style.normal'), value: 'normal' }, + { id: 'italic', name: getMessage('font.style.italic'), value: 'italic' }, + { id: 'bold', name: getMessage('font.style.bold'), value: 'bold' }, + { id: 'boldAndItalic', name: getMessage('font.style.bold.italic'), value: 'boldAndItalic' }, + ] + const fontColors = [ + { id: 'black', name: getMessage('color.black'), value: 'black' }, + { id: 'red', name: getMessage('color.red'), value: 'red' }, + { id: 'blue', name: getMessage('color.blue'), value: 'blue' }, + { id: 'gray', name: getMessage('color.gray'), value: 'gray' }, + { id: 'yellow', name: getMessage('color.yellow'), value: 'yellow' }, + { id: 'green', name: getMessage('color.green'), value: 'green' }, + { id: 'pink', name: getMessage('color.pink'), value: 'pink' }, + { id: 'gold', name: getMessage('color.gold'), value: 'gold' }, + { id: 'darkblue', name: getMessage('color.darkblue'), value: 'darkblue' }, + ] useEffect(() => { - console.log(2, isShow) - if (pixel) { - setOriginPixel(pixels?.filter((data) => data.value === pixel)[0]) + if (originPixel) { + setOriginPixel(pixels?.filter((data) => data.value === originPixel)[0]) } + setIsShow(true) }, []) useEffect(() => { - console.log(1, isShow) + if (originPixel.name) { + setOriginPixel(originPixel) + } + }, [originPixel]) + + useEffect(() => { if (!isShow) { closePopups([fontModalId, colorModalId]) } }, [isShow]) + const handleFontSave = (font) => { + setOriginFont(font.fontFamily) + setOriginFontWeight(font.fontWeight) + setOriginFontSize(font.fontSize) + setOriginFontColor(font.fontColor) + } + const handleColorSave = () => {} + const colorPickerProps = { isShow: showColorPickerModal, setIsShow: setShowColorPickerModal, color: originColor, setColor: setOriginColor, id: colorModalId, + isConfig: true, pos: { x: 495, y: 180, @@ -79,14 +121,13 @@ export default function DimensionLineSetting(props) { const fontProps = { isShow: showFontModal, setIsShow: setShowFontModal, - color: originColor, - setColor: setOriginColor, - font: originFont, - setFont: setOriginFont, - fontColor: 'black', - setFontColor: setOriginFontColor, - fontSize: originFontSize, - setFontSize: setOriginFontSize, + font: { + fontFamily: originFont, + fontWeight: originFontWeight, + fontSize: originFontSize, + fontColor: originFontColor, + }, + onSave: handleFontSave, isConfig: true, id: fontModalId, pos: { @@ -106,6 +147,30 @@ export default function DimensionLineSetting(props) { } } + const onSave = () => { + setGlobalFont((prev) => { + return { + ...prev, + dimensionLineText: { + fontFamily: originFont, + fontWeight: originFontWeight, + fontSize: originFontSize, + fontColor: originFontColor, + }, + fontFlag: true, + } + }) + setDimensionLineSettings((prev) => { + return { + ...prev, + pixel: originPixel.name, + color: originColor, + } + }) + setIsShow(false) + closePopups([fontModalId, colorModalId, id]) + } + return (
@@ -114,8 +179,8 @@ export default function DimensionLineSetting(props) {
@@ -169,26 +235,7 @@ export default function DimensionLineSetting(props) {
-
diff --git a/src/components/floor-plan/modal/setting01/planSize/PlanSizeSetting.jsx b/src/components/floor-plan/modal/setting01/planSize/PlanSizeSetting.jsx index f83648c9..a841efd5 100644 --- a/src/components/floor-plan/modal/setting01/planSize/PlanSizeSetting.jsx +++ b/src/components/floor-plan/modal/setting01/planSize/PlanSizeSetting.jsx @@ -1,17 +1,54 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import { useRecoilValue } from 'recoil' import { useMessage } from '@/hooks/useMessage' import { usePopup } from '@/hooks/usePopup' import WithDraggable from '@/components/common/draggable/WithDraggable' import { canvasState } from '@/store/canvasAtom' +import { useCanvasSetting } from '@/hooks/option/useCanvasSetting' +import { onlyNumberInputChange } from '@/util/input-utils' export default function PlanSizeSetting(props) { - const { horizon, setHorizon, vertical, setVertical, id, pos = { x: 985, y: 180 }, setIsShow } = props + const { setIsShow, horizon, vertical, id, pos = { x: 985, y: 180 } } = props const { closePopup } = usePopup() const { getMessage } = useMessage() - const [originHorizon, setOriginHorizon] = useState(horizon) - const [originVertical, setOriginVertical] = useState(vertical) + const canvas = useRecoilValue(canvasState) + const { planSizeSettingMode, setPlanSizeSettingMode } = useCanvasSetting() + + // 데이터를 최초 한 번만 조회 + useEffect(() => { + console.log('PlanSizeSetting useEffect 실행') + }, []) + + const onSave = () => { + setPlanSizeSettingMode((prev) => { + return { + ...prev, + originHorizon: Number(planSizeSettingMode.originHorizon), + originVertical: Number(planSizeSettingMode.originVertical), + flag: true, + } + }) + + canvas.setWidth(planSizeSettingMode.originHorizon) + canvas.setHeight(planSizeSettingMode.originVertical) + canvas.renderAll() + + setIsShow(false) + closePopup(id, true) + } + + const changeInput = (value, e) => { + const { name } = e.target + console.log('name', name, value) + setPlanSizeSettingMode((prev) => { + return { + ...prev, + [name]: Number(value), + flag: false, + } + }) + } return ( @@ -33,7 +70,15 @@ export default function PlanSizeSetting(props) {
{getMessage('common.horizon')}
- setOriginHorizon(Number(e.target.value))} /> + setPlanSizeSettingMode({ ...planSizeSettingMode, originHorizon: Number(e.target.value), flag: false })} + //onFocus={(e) => (originHorizon.current.value = '')} + onChange={(e) => onlyNumberInputChange(e, changeInput)} + />
mm
@@ -43,26 +88,18 @@ export default function PlanSizeSetting(props) { setOriginVertical(Number(e.target.value))} + name={`originVertical`} + value={planSizeSettingMode.originVertical} + //onChange={(e) => setPlanSizeSettingMode({ ...planSizeSettingMode, originVertical: Number(e.target.value), flag: false })} + //onFocus={(e) => (originVertical.current.value = '')} + onChange={(e) => onlyNumberInputChange(e, changeInput)} />
mm
-
diff --git a/src/components/floor-plan/modal/wallLineOffset/type/Offset.jsx b/src/components/floor-plan/modal/wallLineOffset/type/Offset.jsx index 11fcd84f..418a9419 100644 --- a/src/components/floor-plan/modal/wallLineOffset/type/Offset.jsx +++ b/src/components/floor-plan/modal/wallLineOffset/type/Offset.jsx @@ -5,6 +5,7 @@ import { useEvent } from '@/hooks/useEvent' export default function Offset({ length1Ref, arrow1Ref, currentWallLineRef }) { const { getMessage } = useMessage() const { addDocumentEventListener, initEvent } = useEvent() + // const { addDocumentEventListener, initEvent } = useContext(EventContext) useEffect(() => { addDocumentEventListener('keydown', document, keyDown) diff --git a/src/components/floor-plan/modal/wallLineOffset/type/WallLine.jsx b/src/components/floor-plan/modal/wallLineOffset/type/WallLine.jsx index 15ccefa3..581f57f3 100644 --- a/src/components/floor-plan/modal/wallLineOffset/type/WallLine.jsx +++ b/src/components/floor-plan/modal/wallLineOffset/type/WallLine.jsx @@ -5,6 +5,7 @@ import { useEvent } from '@/hooks/useEvent' export default forwardRef(function WallLine({ length1Ref, length2Ref, arrow1Ref, arrow2Ref, radioTypeRef, currentWallLineRef }, ref) { const { getMessage } = useMessage() const { addDocumentEventListener, initEvent } = useEvent() + // const { addDocumentEventListener, initEvent } = useContext(EventContext) const [type, setType] = useState(1) const [arrow1, setArrow1] = useState('up') const [arrow2, setArrow2] = useState('up') diff --git a/src/components/header/Header.jsx b/src/components/header/Header.jsx index 29cec36b..2674936c 100644 --- a/src/components/header/Header.jsx +++ b/src/components/header/Header.jsx @@ -1,10 +1,10 @@ 'use client' -import { Fragment, useCallback, useEffect, useState } from 'react' +import { Fragment, useCallback, useContext, useEffect, useState } from 'react' import Link from 'next/link' import { usePathname } from 'next/navigation' -import { useRecoilState, useRecoilValue } from 'recoil' +import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil' import { dimmedStore, sessionStore } from '@/store/commonAtom' import { useMessage } from '@/hooks/useMessage' @@ -16,6 +16,9 @@ import UserInfoModal from '@/components/myInfo/UserInfoModal' import { useAxios } from '@/hooks/useAxios' import { globalLocaleStore } from '@/store/localeAtom' +import { stuffSearchState } from '@/store/stuffAtom' +import { QcastContext } from '@/app/QcastProvider' + export const ToggleonMouse = (e, act, target) => { const listWrap = e.target.closest(target) const ListItem = Array.from(listWrap.childNodes) @@ -34,6 +37,8 @@ export const ToggleonMouse = (e, act, target) => { export default function Header(props) { const [userInfoModal, setUserInfoModal] = useState(false) + const [stuffSearch, setStuffSearch] = useRecoilState(stuffSearchState) + const { userSession } = props const [sessionState, setSessionState] = useRecoilState(sessionStore) const { getMessage } = useMessage() @@ -43,6 +48,8 @@ export default function Header(props) { // } const [selected, setSelected] = useState('') + const { isGlobalLoading } = useContext(QcastContext) + const dimmedState = useRecoilValue(dimmedStore) const isDimmed = dimmedState ? 'opacity-50 bg-black' : '' @@ -160,45 +167,56 @@ export default function Header(props) { } return ( - !(pathName.includes('login') || pathName.includes('join') || sessionState.pwdInitYn === 'N') && ( -
-
-
-

- -

- -
-
-
- { - setUserInfoModal(true) - }} - > - - - {userInfoModal && } + <> + {!isGlobalLoading && !(pathName.includes('login') || pathName.includes('join') || sessionState.pwdInitYn === 'N') && ( +
+
+
+

+ +

+
-
- -
-
- -
-
- +
+
+ { + setUserInfoModal(true) + }} + > + + + {userInfoModal && } +
+
+ +
+
+ +
+
+ +
-
-
- ) +
+ )} + ) } diff --git a/src/components/main/MainContents.jsx b/src/components/main/MainContents.jsx index 77df845a..e45cfd33 100644 --- a/src/components/main/MainContents.jsx +++ b/src/components/main/MainContents.jsx @@ -1,4 +1,6 @@ -import React, { useEffect, useState } from 'react' +'use client' + +import { useEffect, useState, useContext } from 'react' import ProductItem from './ProductItem' import { useMessage } from '@/hooks/useMessage' import Image from 'next/image' @@ -8,19 +10,20 @@ import { useRecoilValue } from 'recoil' import { useRouter } from 'next/navigation' import { globalLocaleStore } from '@/store/localeAtom' import { queryStringFormatter } from '@/util/common-utils' -import { sessionStore } from '@/store/commonAtom' import MainSkeleton from '../ui/MainSkeleton' +import { SessionContext } from '@/app/SessionProvider' +import { useMainContentsController } from '@/hooks/main/useMainContentsController' +import { QcastContext } from '@/app/QcastProvider' export default function MainContents() { + const { session } = useContext(SessionContext) const { getMessage } = useMessage() const router = useRouter() const globalLocaleState = useRecoilValue(globalLocaleStore) const { promiseGet } = useAxios(globalLocaleState) - const sessionState = useRecoilValue(sessionStore) - //최근 물건 - const [objectList, setObjectList] = useState([]) + // const [objectList, setObjectList] = useState([]) //공지사항 const [recentNoticeList, setRecentNoticeList] = useState([]) @@ -29,36 +32,42 @@ export default function MainContents() { const [recentFaqList, setRecentFaqList] = useState([]) //Sales Contact info - const [businessCharger, setBusinessCharger] = useState(null) - const [businessChargerMail, setBusinessChargerMail] = useState(null) + // const [businessCharger, setBusinessCharger] = useState(null) + // const [businessChargerMail, setBusinessChargerMail] = useState(null) + + const { qcastState } = useContext(QcastContext) + const { fetchObjectList, initObjectList } = useMainContentsController() useEffect(() => { fetchObjectList() fetchNoticeList() fetchFaqList() - }, [sessionState]) + return () => { + initObjectList() + } + }, []) //최근 갱신 물건목록 / Sales Contact info 정보 - const fetchObjectList = async () => { - try { - const apiUrl = `/api/main-page/object/${sessionState?.storeId}/list` - await promiseGet({ - url: apiUrl, - }).then((res) => { - if (res.status === 200) { - setObjectList(res.data.objectList) - setBusinessCharger(res.data.businessCharger) - setBusinessChargerMail(res.data.businessChargerMail) - } else { - setObjectList([]) - setBusinessCharger(null) - setBusinessChargerMail(null) - } - }) - } catch (error) { - console.error('MAIN API fetching error:', error) - } - } + // const fetchObjectList = async () => { + // try { + // const apiUrl = `/api/main-page/object/${session?.storeId}/list` + // await promiseGet({ + // url: apiUrl, + // }).then((res) => { + // if (res.status === 200) { + // setObjectList(res.data.objectList) + // setBusinessCharger(res.data.businessCharger) + // setBusinessChargerMail(res.data.businessChargerMail) + // } else { + // setObjectList([]) + // setBusinessCharger(null) + // setBusinessChargerMail(null) + // } + // }) + // } catch (error) { + // console.error('MAIN API fetching error:', error) + // } + // } //공지사항 호출 const fetchNoticeList = async () => { @@ -110,9 +119,9 @@ export default function MainContents() {
- {objectList.length > 0 ? ( + {qcastState?.objectList.length > 0 ? (
    - {objectList.map((row) => { + {qcastState?.objectList.map((row) => { return (
  • react
- {(businessCharger &&
{businessCharger}
) || ( + {(qcastState?.businessCharger &&
{qcastState?.businessCharger}
) || (
{getMessage('main.content.noBusiness')}
)} @@ -199,7 +208,7 @@ export default function MainContents() {
react
- {(businessChargerMail &&
{businessChargerMail}
) || ( + {(qcastState?.businessChargerMail &&
{qcastState?.businessChargerMail}
) || (
{getMessage('main.content.noBusiness')}
)} diff --git a/src/components/management/Stuff.jsx b/src/components/management/Stuff.jsx index fb7ecd76..9eb50dca 100644 --- a/src/components/management/Stuff.jsx +++ b/src/components/management/Stuff.jsx @@ -16,7 +16,11 @@ import JA from '@/locales/ja.json' import QPagination from '../common/pagination/QPagination' import { SessionContext } from '@/app/SessionProvider' +import { QcastContext } from '@/app/QcastProvider' + export default function Stuff() { + const { setIsGlobalLoading } = useContext(QcastContext) + const resetStuffRecoil = useResetRecoilState(stuffSearchState) const { session } = useContext(SessionContext) const setAppMessageState = useSetRecoilState(appMessageStore) @@ -39,14 +43,15 @@ export default function Stuff() { const copyNo = async (value) => { try { await navigator.clipboard.writeText(value) - alert(getMessage('stuff.detail.header.message2')) + alert(getMessage('stuff.detail.header.successCopy')) } catch (error) { - alert(getMessage('stuff.detail.header.message3')) + alert(getMessage('stuff.detail.header.failCopy')) } } //물건번호 복사버튼 옆에 영역 const onDoubleClick = (data) => { + setIsGlobalLoading(true) if (data.tempFlg === '0') { router.push(`${pathname}/detail?objectNo=${data.objectNo.toString()}`, { scroll: false }) } else { @@ -149,6 +154,7 @@ export default function Stuff() { } else { //T 면 임시 R은 진짜 if (event.data.objectNo) { + setIsGlobalLoading(true) if (event.data.tempFlg === '0') { router.push(`${pathname}/detail?objectNo=${event.data.objectNo.toString()}`, { scroll: false }) } else { @@ -172,12 +178,14 @@ export default function Stuff() { schDateType: stuffSearchParams.schDateType, schFromDt: dayjs(new Date()).add(-1, 'year').format('YYYY-MM-DD'), schToDt: dayjs(new Date()).format('YYYY-MM-DD'), - startRow: (pageNo - 1) * pageSize + 1, - endRow: pageNo * pageSize, - schSelSaleStoreId: stuffSearchParams?.schOtherSelSaleStoreId ? stuffSearchParams.schOtherSelSaleStoreId : stuffSearchParams.schSelSaleStoreId, + startRow: (stuffSearch.pageNo - 1) * stuffSearchParams.pageSize + 1, + endRow: stuffSearchParams?.endRow, + schSelSaleStoreId: stuffSearchParams?.schSelSaleStoreId ? stuffSearchParams.schSelSaleStoreId : '', + schOtherSelSaleStoreId: stuffSearchParams?.schOtherSelSaleStoreId ? stuffSearchParams.schOtherSelSaleStoreId : '', schSortType: stuffSearchParams.schSortType, + pageNo: stuffSearchParams?.pageNo ? stuffSearchParams.pageNo : 1, + pageSize: stuffSearchParams?.pageSize ? stuffSearchParams.pageSize : 100, } - async function fetchData() { const apiUrl = `/api/object/list?${queryStringFormatter(params)}` await get({ @@ -186,15 +194,21 @@ export default function Stuff() { if (!isEmptyArray(res)) { setGridProps({ ...gridProps, gridData: res, count: res[0].totCnt }) setTotalCount(res[0].totCnt) + } else { + setGridProps({ ...gridProps, gridData: [], count: 0 }) + setTotalCount(0) + + setPageNo(1) + setPageSize(stuffSearchParams.pageSize) + stuffSearchParams.pageNo = 1 + stuffSearchParams.startRow = 1 + stuffSearchParams.endRow = 1 * stuffSearchParams.pageSize } + + setIsGlobalLoading(false) }) } - - //if (session.storeId === 'T01') { fetchData() - //} else if (stuffSearch.schSelSaleStoreId !== '') { - //fetchData() - //} } else if (stuffSearchParams?.code === 'M') { const params = { saleStoreId: session?.storeId, @@ -211,19 +225,47 @@ export default function Stuff() { endRow: pageNo * pageSize, schSelSaleStoreId: stuffSearchParams?.schOtherSelSaleStoreId ? stuffSearchParams.schOtherSelSaleStoreId : stuffSearchParams.schSelSaleStoreId, schSortType: 'R', + code: 'S', + pageNo: 1, + pageSize: 100, } setStuffSearch({ ...params, }) } else if (stuffSearchParams?.code === 'E') { + stuffSearchParams.startRow = (stuffSearch.pageNo - 1) * stuffSearchParams.pageSize + 1 + stuffSearchParams.endRow = stuffSearchParams.pageNo * stuffSearchParams.pageSize + stuffSearchParams.schSortType = defaultSortType + stuffSearchParams.pageNo = stuffSearchParams.pageNo + + async function fetchData() { + const apiUrl = `/api/object/list?saleStoreId=${session?.storeId}&${queryStringFormatter(stuffSearchParams)}` + await get({ url: apiUrl }).then((res) => { + if (!isEmptyArray(res)) { + setGridProps({ ...gridProps, gridData: res, count: res[0].totCnt }) + setTotalCount(res[0].totCnt) + } else { + setGridProps({ ...gridProps, gridData: [], count: 0 }) + setTotalCount(0) + + setPageNo(1) + setPageSize(stuffSearchParams.pageSize) + stuffSearchParams.pageNo = 1 + stuffSearchParams.startRow = 1 + stuffSearchParams.endRow = 1 * stuffSearchParams.pageSize + } + + setIsGlobalLoading(false) + }) + } + fetchData() + } else if (stuffSearchParams?.code === 'C') { + resetStuffRecoil() + } else if (stuffSearchParams?.code === 'FINISH') { stuffSearchParams.startRow = 1 stuffSearchParams.endRow = 1 * pageSize stuffSearchParams.schSortType = defaultSortType setPageNo(1) - setStuffSearch({ - ...stuffSearch, - code: 'FINISH', - }) async function fetchData() { const apiUrl = `/api/object/list?saleStoreId=${session?.storeId}&${queryStringFormatter(stuffSearchParams)}` @@ -237,10 +279,32 @@ export default function Stuff() { } }) } - fetchData() - } else if (stuffSearchParams?.code === 'C') { - resetStuffRecoil() + } else if (stuffSearchParams?.code === 'DELETE') { + const newParams = { + saleStoreId: session.storeId, + schObjectNo: '', + schAddress: '', + schObjectName: '', + schSaleStoreName: '', + schReceiveUser: '', + schDispCompanyName: '', + schDateType: 'U', + schFromDt: dayjs(new Date()).add(-1, 'year').format('YYYY-MM-DD'), + schToDt: dayjs(new Date()).format('YYYY-MM-DD'), + startRow: 1, + endRow: 100, + schSelSaleStoreId: '', + schOtherSelSaleStoreId: '', + schSortType: 'R', + code: 'S', + pageNo: 1, + pageSize: 100, + } + + setStuffSearch({ + ...newParams, + }) } }, [stuffSearchParams]) @@ -249,59 +313,46 @@ export default function Stuff() { let startRow = (1 - 1) * e.target.value + 1 stuffSearchParams.startRow = startRow stuffSearchParams.endRow = 1 * e.target.value - stuffSearchParams.schSelSaleStoreId = stuffSearchParams?.schOtherSelSaleStoreId - ? stuffSearchParams.schOtherSelSaleStoreId - : stuffSearchParams.schSelSaleStoreId + stuffSearchParams.schSelSaleStoreId = stuffSearchParams.schSelSaleStoreId + stuffSearchParams.schOtherSelSaleStoreId = stuffSearchParams.schOtherSelSaleStoreId + stuffSearchParams.pageNo = startRow + stuffSearchParams.pageSize = 1 * e.target.value setPageSize(e.target.value) - setStuffSearch({ - ...stuffSearchParams, - startRow: startRow, - endRow: 1 * e.target.value, - }) - - setPageNo(1) - const apiUrl = `/api/object/list?saleStoreId=${session?.storeId}&${queryStringFormatter(stuffSearchParams)}` - get({ url: apiUrl }).then((res) => { - if (!isEmptyArray(res)) { - setGridProps({ ...gridProps, gridData: res, count: res[0].totCnt }) - setTotalCount(res[0].totCnt) - } else { - setGridProps({ ...gridProps, gridData: [], count: 0 }) - setTotalCount(0) - } - }) - } - - //최근 등록일 수정일 정렬 이벤트 - const onChangeSortType = (e) => { - let startRow = (1 - 1) * pageSize + 1 - stuffSearchParams.startRow = startRow - stuffSearchParams.endRow = 1 * pageSize - - stuffSearchParams.schSortType = e.target.value - stuffSearchParams.schSelSaleStoreId = stuffSearchParams?.schOtherSelSaleStoreId - ? stuffSearchParams.schOtherSelSaleStoreId - : stuffSearchParams.schSelSaleStoreId - setDefaultSortType(e.target.value) setStuffSearch({ ...stuffSearch, code: 'S', startRow: startRow, - endRow: 1 * pageSize, + endRow: 1 * e.target.value, + pageSize: e.target.value, + }) + + setPageNo(1) + } + + //최근 등록일 수정일 정렬 이벤트 + const onChangeSortType = (e) => { + // let startRow = (stuffSearchParams.pageNo - 1) * pageSize + 1 + let startRow = (1 - 1) * stuffSearchParams.pageSize + 1 + stuffSearchParams.startRow = startRow + stuffSearchParams.endRow = startRow * stuffSearchParams.pageSize + stuffSearchParams.schSelSaleStoreId = stuffSearchParams.schSelSaleStoreId + stuffSearchParams.schOtherSelSaleStoreId = stuffSearchParams.schOtherSelSaleStoreId + stuffSearchParams.pageNo = startRow + stuffSearchParams.pageSize = 1 * stuffSearchParams.pageSize + setPageSize(stuffSearchParams.pageSize) + stuffSearchParams.schSortType = e.target.value + setDefaultSortType(e.target.value) + + setStuffSearch({ + ...stuffSearch, + code: 'S', + startRow: startRow, + endRow: startRow * stuffSearchParams.pageSize, + pageSize: stuffSearchParams.pageSize, schSortType: e.target.value, }) setPageNo(1) - const apiUrl = `/api/object/list?saleStoreId=${session?.storeId}&${queryStringFormatter(stuffSearchParams)}` - get({ url: apiUrl }).then((res) => { - if (!isEmptyArray(res)) { - setGridProps({ ...gridProps, gridData: res, count: res[0].totCnt }) - setTotalCount(res[0].totCnt) - } else { - setGridProps({ ...gridProps, gridData: [], count: 0 }) - setTotalCount(0) - } - }) } useEffect(() => { @@ -315,19 +366,24 @@ export default function Stuff() { // 페이징 현재페이지 변경 const handleChangePage = (page) => { stuffSearchParams.code = 'S' - stuffSearchParams.schSelSaleStoreId = stuffSearchParams?.schOtherSelSaleStoreId - ? stuffSearchParams.schOtherSelSaleStoreId - : stuffSearchParams.schSelSaleStoreId + stuffSearchParams.schSelSaleStoreId = stuffSearchParams.schSelSaleStoreId + stuffSearchParams.schOtherSelSaleStoreId = stuffSearchParams.schOtherSelSaleStoreId + setStuffSearch({ ...stuffSearch, code: 'S', startRow: (page - 1) * pageSize + 1, - endRow: page * pageSize, + endRow: page * stuffSearchParams?.pageSize, + pageNo: page, }) setPageNo(page) } + useEffect(() => { + setIsGlobalLoading(true) + }, []) + return ( <> {/* 퍼블시작 */} @@ -345,13 +401,13 @@ export default function Stuff() {
-
- @@ -363,7 +419,13 @@ export default function Stuff() {
- +
diff --git a/src/components/management/StuffDetail.jsx b/src/components/management/StuffDetail.jsx index 602c6354..080a7cb6 100644 --- a/src/components/management/StuffDetail.jsx +++ b/src/components/management/StuffDetail.jsx @@ -10,7 +10,7 @@ import { globalLocaleStore } from '@/store/localeAtom' import { isEmptyArray, isNotEmptyArray, isObjectNotEmpty } from '@/util/common-utils' import { useMessage } from '@/hooks/useMessage' import { useForm } from 'react-hook-form' -import { useRecoilValue, useSetRecoilState } from 'recoil' +import { useRecoilValue, useSetRecoilState, useResetRecoilState, useRecoilState } from 'recoil' import { SessionContext } from '@/app/SessionProvider' import FindAddressPop from './popup/FindAddressPop' import PlanRequestPop from './popup/PlanRequestPop' @@ -18,10 +18,21 @@ import WindSelectPop from './popup/WindSelectPop' import { useCommonCode } from '@/hooks/common/useCommonCode' import StuffPlanQGrid from './StuffPlanQGrid' import { floorPlanObjectState } from '@/store/floorPlanObjectAtom' - +import { ManagementContext } from '@/app/management/ManagementProvider' +import DocDownOptionPop from '../estimate/popup/DocDownOptionPop' +import { stuffSearchState } from '@/store/stuffAtom' +import { QcastContext } from '@/app/QcastProvider' export default function StuffDetail() { + const { setIsGlobalLoading } = useContext(QcastContext) + const resetStuffRecoil = useResetRecoilState(stuffSearchState) + const stuffSearchParams = useRecoilValue(stuffSearchState) + const setFloorPlanObjectNo = useSetRecoilState(floorPlanObjectState) //견적서 화면용 물건번호리코일 + const [estimatePopupOpen, setEstimatePopupOpen] = useState(false) + + const [popPlanNo, setPopPlanNo] = useState('1') //default 1 + //공통코드 const { commonCode, findCommonCode } = useCommonCode() const [selOptions, setSelOptions] = useState('') //선택한 1차점 @@ -96,7 +107,7 @@ export default function StuffDetail() { const objectNo = searchParams.get('objectNo') //url에서 물건번호 꺼내서 바로 set const [editMode, setEditMode] = useState('NEW') - const [detailData, setDetailData] = useState({}) + const { managementState, setManagementState } = useContext(ManagementContext) const [planGridProps, setPlanGridProps] = useState({ planGridData: [], @@ -131,6 +142,16 @@ export default function StuffDetail() { headerName: getMessage('stuff.detail.planGridHeader.capacity'), width: 120, cellStyle: { justifyContent: 'flex-end' /* 우측정렬*/ }, + cellRenderer: (params) => { + let origin = params.value + let capacity + if (origin) { + capacity = origin / 1000 + return capacity.toFixed(3) + } else { + return null + } + }, }, { field: 'roofMaterialIdMulti', @@ -159,8 +180,8 @@ export default function StuffDetail() { }, }, { - field: 'constructSpecification', - headerName: getMessage('stuff.detail.planGridHeader.constructSpecification'), + field: 'constructSpecificationMulti', + headerName: getMessage('stuff.detail.planGridHeader.constructSpecificationMulti'), wrapText: true, autoHeight: true, cellStyle: { justifyContent: 'flex-start' /* 좌측정렬*/ }, @@ -265,11 +286,12 @@ export default function StuffDetail() { type="button" className="grid-btn" onClick={() => { - console.log('엑셀버튼클릭') + setFloorPlanObjectNo({ floorPlanObjectNo: params.data.objectNo }) + handleEstimatePopup(params.data.planNo) }} > - {getMessage('stuff.detail.planGrid.btn2')} + {getMessage('stuff.detail.planGrid.docDownload')}
@@ -279,6 +301,12 @@ export default function StuffDetail() { ], }) + // 문서다운로드 팝업 오픈 셋팅 + const handleEstimatePopup = (planNo) => { + setPopPlanNo(planNo) + setEstimatePopupOpen(true) + } + useEffect(() => { if (objectNo) { setEditMode('EDIT') @@ -288,10 +316,12 @@ export default function StuffDetail() { } promiseGet({ url: `/api/object/${objectNo}/detail` }).then((res) => { if (res.status === 200) { - if (res.data != null) { - setDetailData(res.data) + if (isObjectNotEmpty(res.data)) { + setManagementState(res.data) } else { - setDetailData({}) + setManagementState({}) + alert(getMessage('stuff.detail.header.notExistObjectNo')) + router.push('/management/stuff') } if (isNotEmptyArray(res.data.planList)) { setPlanGridProps({ ...planGridProps, planGridData: res.data.planList }) @@ -299,8 +329,11 @@ export default function StuffDetail() { setPlanGridProps({ ...planGridProps, planGridData: [] }) } } else { - setDetailData({}) + setManagementState({}) setPlanGridProps({ ...planGridProps, planGridData: [] }) + + alert(getMessage('stuff.detail.header.notExistObjectNo')) + router.push('/management/stuff') } }) } else { @@ -396,6 +429,8 @@ export default function StuffDetail() { } } }) + + setIsGlobalLoading(false) } }, [objectNo, session]) @@ -415,158 +450,165 @@ export default function StuffDetail() { }, [commonCode]) useEffect(() => { - if (isObjectNotEmpty(detailData)) { - // 도도부현API - get({ url: '/api/object/prefecture/list' }).then((res) => { - if (!isEmptyArray(res)) { - setPrefCodeList(res) - } - }) + if (objectNo) { + if (isObjectNotEmpty(managementState)) { + // 도도부현API + get({ url: '/api/object/prefecture/list' }).then((res) => { + if (!isEmptyArray(res)) { + setPrefCodeList(res) + } + }) - //1차점 : X167 T01 - //2차점 : 10X22, 201X112 - let url - let firstList - let otherList - let favList + //1차점 : X167 T01 + //2차점 : 10X22, 201X112 + let url + let firstList + let otherList + let favList - if (session?.storeId === 'T01') { - url = `/api/object/saleStore/${session?.storeId}/firstList?userId=${session?.userId}` - } else { - if (session.storeLvl === '1') { - url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=1&userId=${session?.userId}` + if (session?.storeId === 'T01') { + url = `/api/object/saleStore/${session?.storeId}/firstList?userId=${session?.userId}` } else { - url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=1&userId=${session?.userId}` - } - } - get({ url: url }).then((res) => { - if (!isEmptyArray(res)) { - if (session?.storeId === 'T01') { - firstList = res.filter((row) => row.saleStoreLevel === '1') - firstList.sort((a, b) => (a.saleStoreId !== 'T01') - (b.saleStoreId !== 'T01') || a.saleStoreId - b.saleStoreId) - favList = firstList.filter((row) => row.saleStoreId === 'T01' || row.priority !== 'B') - setSaleStoreList(firstList) - setFavoriteStoreList(favList) - setShowSaleStoreList(favList) - - if (detailData.firstAgentId != null) { - form.setValue('saleStoreId', detailData.firstAgentId) - setSelOptions(detailData.firstAgentId) - } else { - form.setValue('saleStoreId', detailData.saleStoreId) - setSelOptions(detailData.saleStoreId) - } - - //상세데이터의 1차점 아이디로 2차점 목록 조회하기 - - let data = detailData?.firstAgentId ? detailData.firstAgentId : detailData.saleStoreId - // url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=0&userId=${session?.userId}` - url = `/api/object/saleStore/${data}/list?firstFlg=0&userId=${session?.userId}` - - get({ url: url }).then((res) => { - if (!isEmptyArray(res)) { - res.map((row) => { - row.value = row.saleStoreId - row.label = row.saleStoreName - }) - - otherList = res - setOriginOtherSaleStoreList(otherList) - setOtherSaleStoreList(otherList) - } - }) + if (session.storeLvl === '1') { + url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=1&userId=${session?.userId}` } else { - //1차점 셀렉트박스 - if (session?.storeLvl === '1') { - firstList = res - favList = res.filter((row) => row.priority !== 'B') - otherList = res.filter((row) => row.firstAgentYn === 'N') - - setSaleStoreList(firstList) - setFavoriteStoreList(firstList) - setShowSaleStoreList(firstList) - - setOtherSaleStoreList(otherList) - } else { - setSelOptions(res[0].saleStoreId) - form.setValue('saleStoreId', res[0].saleStoreId) - form.setValue('saleStoreLevel', res[0].storeLvl) - setSaleStoreList(res) - setFavoriteStoreList(res) - setShowSaleStoreList(res) - otherList = res.filter((row) => row.firstAgentYn === 'N') - setOtherSaleStoreList(otherList) - } + url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=1&userId=${session?.userId}` } } + get({ url: url }).then((res) => { + if (!isEmptyArray(res)) { + if (session?.storeId === 'T01') { + firstList = res.filter((row) => row.saleStoreLevel === '1') + firstList.sort((a, b) => (a.saleStoreId !== 'T01') - (b.saleStoreId !== 'T01') || a.saleStoreId - b.saleStoreId) + favList = firstList.filter((row) => row.saleStoreId === 'T01' || row.priority !== 'B') + setSaleStoreList(firstList) + setFavoriteStoreList(favList) + setShowSaleStoreList(favList) - //상세데이터가 1차점이면 1차점에 세팅 - //상세데이터가 2차점이면 2차점에 세팅하고 세션으로 1차점 세팅 - if (detailData.saleStoreLevel === '1') { - setSelOptions(detailData.saleStoreId) - form.setValue('saleStoreId', detailData.saleStoreId) - form.setValue('saleStoreLevel', detailData.saleStoreLevel) - } else { - setOtherSelOptions(detailData.saleStoreId) - form.setValue('otherSaleStoreId', detailData.saleStoreId) - form.setValue('otherSaleStoreLevel', detailData.saleStoreLevel) - } + if (managementState.firstAgentId != null) { + form.setValue('saleStoreId', managementState.firstAgentId) + setSelOptions(managementState.firstAgentId) + } else { + form.setValue('saleStoreId', managementState.saleStoreId) + setSelOptions(managementState.saleStoreId) + } - //설계의뢰No. - form.setValue('planReqNo', detailData.planReqNo) - //담당자 - form.setValue('receiveUser', detailData.receiveUser) + //상세데이터의 1차점 아이디로 2차점 목록 조회하기 - //물건구분objectStatusId - setSelectObjectStatusId(detailData.objectStatusId) - form.setValue('objectStatusId', detailData.objectStatusId) + let data = managementState?.firstAgentId ? managementState.firstAgentId : managementState.saleStoreId + url = `/api/object/saleStore/${data}/list?firstFlg=0&userId=${session?.userId}` - //물건명 - form.setValue('objectName', detailData.objectName) + get({ url: url }).then((res) => { + if (!isEmptyArray(res)) { + res.map((row) => { + row.value = row.saleStoreId + row.label = row.saleStoreName + }) - //경칭코드 - setSelHonorificCode(detailData.objectNameOmit) - form.setValue('objectNameOmit', detailData.objectNameOmit) + otherList = res + setOriginOtherSaleStoreList(otherList) + setOtherSaleStoreList(otherList) + } + }) + } else { + //1차점 셀렉트박스 + if (session?.storeLvl === '1') { + firstList = res + favList = res.filter((row) => row.priority !== 'B') + otherList = res.filter((row) => row.firstAgentYn === 'N') - //물건명 후리가나 - form.setValue('objectNameKana', detailData.objectNameKana) + setSaleStoreList(firstList) + setFavoriteStoreList(firstList) + setShowSaleStoreList(firstList) - //우편번호 - form.setValue('zipNo', detailData.zipNo) + setOtherSaleStoreList(otherList) + } else { + setSelOptions(res[0].saleStoreId) + form.setValue('saleStoreId', res[0].saleStoreId) + form.setValue('saleStoreLevel', res[0].storeLvl) + setSaleStoreList(res) + setFavoriteStoreList(res) + setShowSaleStoreList(res) + otherList = res.filter((row) => row.firstAgentYn === 'N') + setOtherSaleStoreList(otherList) + } + } + } - //도도부현 / 주소 - setPrefValue(detailData.prefId) - form.setValue('prefId', detailData.prefId) - form.setValue('address', detailData.address) - //발전시뮬 - form.setValue('areaId', detailData.areaId) + //상세데이터가 1차점이면 1차점에 세팅 + //상세데이터가 2차점이면 2차점에 세팅하고 세션으로 1차점 세팅 + if (managementState.saleStoreLevel === '1') { + setSelOptions(managementState.saleStoreId) + form.setValue('saleStoreId', managementState.saleStoreId) + form.setValue('saleStoreLevel', managementState.saleStoreLevel) + } else { + setOtherSelOptions(managementState.saleStoreId) + form.setValue('otherSaleStoreId', managementState.saleStoreId) + form.setValue('otherSaleStoreLevel', managementState.saleStoreLevel) - //기준풍속 - form.setValue('standardWindSpeedId', detailData.standardWindSpeedId) - //수직적설량 - form.setValue('verticalSnowCover', detailData.verticalSnowCover) - //한랭지대책시행 coldRegionFlg 1이면 true - form.setValue('coldRegionFlg', detailData.coldRegionFlg === '1' ? true : false) + form.setValue('saleStoreLevel', '1') + } - //면조도구분 surfaceType null로 내려오면 셋팅 안하고 저장할때 필수값 체크하도록 - // form.setValue('surfaceType', 'Ⅱ') - // form.setValue('surfaceType', 'Ⅲ・Ⅳ') - form.setValue('surfaceType', detailData.surfaceType) - //염해지역용아이템사용 saltAreaFlg 1이면 true - form.setValue('saltAreaFlg', detailData.saltAreaFlg === '1' ? true : false) - //설치높이 - form.setValue('installHeight', detailData.installHeight) - //계약조건 null로 내려오면 0으로 디폴트셋팅 - if (detailData.conType === null) { - form.setValue('conType', '0') - } else { - form.setValue('conType', detailData.conType) - } - //메모 - form.setValue('remarks', detailData.remarks) - }) + //설계의뢰No. + form.setValue('planReqNo', managementState.planReqNo) + //담당자 + form.setValue('receiveUser', managementState.receiveUser) + + //물건구분objectStatusId + setSelectObjectStatusId(managementState.objectStatusId) + form.setValue('objectStatusId', managementState.objectStatusId) + + //물건명 + form.setValue('objectName', managementState.objectName) + + //경칭코드 + setSelHonorificCode(managementState.objectNameOmit) + form.setValue('objectNameOmit', managementState.objectNameOmit) + + //물건명 후리가나 + form.setValue('objectNameKana', managementState.objectNameKana) + + //우편번호 + form.setValue('zipNo', managementState.zipNo) + + //도도부현 / 주소 + setPrefValue(managementState.prefId) + form.setValue('prefId', managementState.prefId) + form.setValue('prefName', managementState.prefName) + form.setValue('address', managementState.address) + //발전시뮬 + form.setValue('areaId', managementState.areaId) + + //기준풍속 + form.setValue('standardWindSpeedId', managementState.standardWindSpeedId) + //수직적설량 + form.setValue('verticalSnowCover', managementState.verticalSnowCover) + //한랭지대책시행 coldRegionFlg 1이면 true + form.setValue('coldRegionFlg', managementState.coldRegionFlg === '1' ? true : false) + + //면조도구분 surfaceType null로 내려오면 셋팅 안하고 저장할때 필수값 체크하도록 + // form.setValue('surfaceType', 'Ⅱ') + // form.setValue('surfaceType', 'Ⅲ・Ⅳ') + form.setValue('surfaceType', managementState.surfaceType) + //염해지역용아이템사용 saltAreaFlg 1이면 true + form.setValue('saltAreaFlg', managementState.saltAreaFlg === '1' ? true : false) + //설치높이 + form.setValue('installHeight', managementState.installHeight) + //계약조건 null로 내려오면 0으로 디폴트셋팅 + if (managementState.conType === null) { + form.setValue('conType', '0') + } else { + form.setValue('conType', managementState.conType) + } + //메모 + form.setValue('remarks', managementState.remarks) + }) + + //상세끝 + setIsGlobalLoading(false) + } } - }, [detailData, session]) + }, [managementState]) //경칭선택 변경 이벤트 const onChangeHonorificCode = (key) => { @@ -856,6 +898,7 @@ export default function StuffDetail() { const setPlanReqInfo = (info) => { form.setValue('planReqNo', info.planReqNo) form.setValue('objectStatusId', info.building) + setSelectObjectStatusId(info.building) form.setValue('objectName', info.planReqName) form.setValue('zipNo', info.zipNo) form.setValue('address', info.address2) @@ -872,8 +915,11 @@ export default function StuffDetail() { form.setValue('standardWindSpeedId', `WL_${info.windSpeed}`) form.setValue('verticalSnowCover', info.verticalSnowCover) form.setValue('surfaceType', info.surfaceType) + if (info.surfaceType === 'Ⅱ') { form.setValue('saltAreaFlg', true) + } else { + form.setValue('saltAreaFlg', false) } form.setValue('installHeight', info.installHeight) form.setValue('remarks', info.remarks) @@ -1034,7 +1080,12 @@ export default function StuffDetail() { //설계의뢰 팝업 오픈 const onSearchDesignRequestPopOpen = () => { - setShowDesignRequestButtonValid(true) + const saleStoreId = form.watch('saleStoreId') + if (saleStoreId === '') { + alert(getMessage('stuff.planReqPopup.error.message2')) + } else { + setShowDesignRequestButtonValid(true) + } } // 풍속선택 팝업 오픈 @@ -1177,9 +1228,9 @@ export default function StuffDetail() { return alert(getMessage('stuff.detail.save.valierror2')) } - let detail_sort = Object.keys(detailData) + let detail_sort = Object.keys(managementState) .sort() - .reduce((obj, key) => ((obj[key] = detailData[key]), obj), {}) + .reduce((obj, key) => ((obj[key] = managementState[key]), obj), {}) let params_sort = Object.keys(params) .sort() .reduce((obj, key) => ((obj[key] = params[key]), obj), {}) @@ -1238,7 +1289,7 @@ export default function StuffDetail() { if (res.status === 201) { alert(getMessage('stuff.detail.save')) setFloorPlanObjectNo({ floorPlanObjectNo: objectNo }) - router.push(`/management/stuff/detail?objectNo=${res.data.objectNo.toString()}`) + router.push(`/management/stuff/detail?objectNo=${res.data.objectNo.toString()}`, { scroll: false }) } }) } else { @@ -1247,8 +1298,7 @@ export default function StuffDetail() { if (res.status === 201) { setFloorPlanObjectNo({ floorPlanObjectNo: res.data.objectNo }) alert(getMessage('stuff.detail.save')) - // router.refresh() - router.push(`/management/stuff/detail?objectNo=${res.data.objectNo.toString()}`) + router.push(`/management/stuff/detail?objectNo=${res.data.objectNo.toString()}`, { scroll: false }) } }) } @@ -1283,6 +1333,7 @@ export default function StuffDetail() { tempFlg: '1', workNo: null, workName: null, + objectNo: objectNo ? objectNo : '', } //1차점 or 2차점 안고르고 임시저장하면 @@ -1291,23 +1342,38 @@ export default function StuffDetail() { params.saleStoreLevel = session.storeLvl } - await promisePost({ url: '/api/object/save-object', data: params }).then((res) => { - if (res.status === 201) { - alert(getMessage('stuff.detail.tempSave.message1')) - router.push(`${pathname}?objectNo=${res.data.objectNo.toString()}`) - } - }) + const apiUrl = '/api/object/save-object' + if (objectNo) { + await promisePut({ url: apiUrl, data: params }).then((res) => { + if (res.status === 201) { + alert(getMessage('stuff.detail.tempSave.message1')) + router.push(`/management/stuff/tempdetail?objectNo=${res.data.objectNo.toString()}`, { scroll: false }) + } + }) + } else { + await promisePost({ url: apiUrl, data: params }).then((res) => { + if (res.status === 201) { + alert(getMessage('stuff.detail.tempSave.message1')) + router.push(`/management/stuff/tempdetail?objectNo=${res.data.objectNo.toString()}`, { scroll: false }) + } + }) + } } // 물건삭제 const onDelete = () => { - const specificationConfirmDate = detailData.specificationConfirmDate + const specificationConfirmDate = managementState.specificationConfirmDate if (specificationConfirmDate != null) { alert(getMessage('stuff.detail.delete.message1')) } else { if (confirm(getMessage('common.message.data.delete'))) { del({ url: `/api/object/${objectNo}` }).then(() => { setFloorPlanObjectNo({ floorPlanObjectNo: '' }) + if (session.storeId === 'T01') { + stuffSearchParams.code = 'DELETE' + } else { + resetStuffRecoil() + } router.push('/management/stuff') }) } @@ -1726,31 +1792,33 @@ export default function StuffDetail() {
-
- { - handleRadioChange(e) - }} - /> - -
-
- { - handleRadioChange(e) - }} - /> - +
+
+ { + handleRadioChange(e) + }} + /> + +
+
+ { + handleRadioChange(e) + }} + /> + +
@@ -1846,8 +1914,7 @@ export default function StuffDetail() {
- {/* {detailData?.tempFlg === '1' && form.watch('planReqNo') ? ( */} - {detailData?.tempFlg === '1' && form.watch('planReqNo') ? ( + {managementState?.tempFlg === '1' && form.watch('planReqNo') ? ( ) : null}
- {/* {detailData?.tempFlg === '1' ? ( */} - {detailData?.tempFlg === '1' ? ( + {managementState?.tempFlg === '1' ? ( <>
{getMessage('stuff.detail.header.specificationConfirmDate')}
-
{headerData.specificationConfirmDate}
+
{managementState?.specificationConfirmDate}
{getMessage('stuff.detail.header.lastEditDatetime')}
- {headerData?.lastEditDatetime ? `${dayjs(headerData.lastEditDatetime).format('YYYY.MM.DD HH:mm:ss')}` : ''}{' '} - {headerData?.lastEditUserName ? `(${headerData.lastEditUserName})` : null} + {managementState?.lastEditDatetime ? `${dayjs(managementState.lastEditDatetime).format('YYYY.MM.DD HH:mm:ss')}` : ''}{' '} + {managementState?.lastEditUserName ? `(${managementState.lastEditUserName})` : null}
{getMessage('stuff.detail.header.createDatetime')}
- {headerData?.createDatetime ? `${dayjs(headerData.lastEditDatetime).format('YYYY.MM.DD')}` : ''}{' '} - {headerData?.createUserName ? `(${headerData.createUserName})` : null} + {managementState?.createDatetime ? `${dayjs(managementState.lastEditDatetime).format('YYYY.MM.DD')}` : ''}{' '} + {managementState?.createUserName ? `(${managementState.createUserName})` : null}
diff --git a/src/components/management/StuffSearchCondition.jsx b/src/components/management/StuffSearchCondition.jsx index 59a4d23c..2efdee3e 100644 --- a/src/components/management/StuffSearchCondition.jsx +++ b/src/components/management/StuffSearchCondition.jsx @@ -17,6 +17,8 @@ import { isObjectNotEmpty } from '@/util/common-utils' import { SessionContext } from '@/app/SessionProvider' +import { QcastContext } from '@/app/QcastProvider' + export default function StuffSearchCondition() { const { session } = useContext(SessionContext) const setAppMessageState = useSetRecoilState(appMessageStore) @@ -66,30 +68,167 @@ export default function StuffSearchCondition() { const [otherSaleStoreList, setOtherSaleStoreList] = useState([]) //1차점 이외 판매점목록 const [otherSaleStoreId, setOtherSaleStoreId] = useState('') + const { setIsGlobalLoading } = useContext(QcastContext) + // 조회 const onSubmit = () => { let diff = dayjs(endDate).diff(startDate, 'day') + if (diff > 366) { return alert(getMessage('stuff.message.periodError')) } + if (isNaN(diff)) { + return alert(getMessage('stuff.message.periodError')) + } + + setIsGlobalLoading(true) + if (stuffSearch.code === 'S') { + if (stuffSearch.pageNo !== 1) { + setStuffSearch({ + schObjectNo: objectNo ? objectNo : stuffSearch.schObjectNo, + schSaleStoreName: saleStoreName ? saleStoreName : '', + schAddress: address ? address : '', + schObjectName: objectName ? objectName : '', + schDispCompanyName: dispCompanyName ? dispCompanyName : '', + schSelSaleStoreId: stuffSearch?.schSelSaleStoreId ? stuffSearch.schSelSaleStoreId : '', + schOtherSelSaleStoreId: stuffSearch?.schOtherSelSaleStoreId ? stuffSearch.schOtherSelSaleStoreId : '', + schReceiveUser: receiveUser ? receiveUser : '', + schDateType: dateType, + schFromDt: startDate ? dayjs(startDate).format('YYYY-MM-DD') : '', + schToDt: endDate ? dayjs(endDate).format('YYYY-MM-DD') : '', + code: 'E', + startRow: 1, + endRow: 1 * stuffSearch?.pageSize, + schSortType: stuffSearch?.schSortType ? stuffSearch.schSortType : 'R', + pageNo: 1, + pageSize: stuffSearch?.pageSize, + }) + } else { + setStuffSearch({ + schObjectNo: objectNo ? objectNo : stuffSearch.schObjectNo, + schSaleStoreName: saleStoreName ? saleStoreName : '', + schAddress: address ? address : '', + schObjectName: objectName ? objectName : '', + schDispCompanyName: dispCompanyName ? dispCompanyName : '', + schSelSaleStoreId: stuffSearch?.schSelSaleStoreId ? stuffSearch.schSelSaleStoreId : '', + schOtherSelSaleStoreId: stuffSearch?.schOtherSelSaleStoreId ? stuffSearch.schOtherSelSaleStoreId : '', + schReceiveUser: receiveUser ? receiveUser : '', + schDateType: dateType, + schFromDt: startDate ? dayjs(startDate).format('YYYY-MM-DD') : '', + schToDt: endDate ? dayjs(endDate).format('YYYY-MM-DD') : '', + code: 'E', + startRow: stuffSearch?.startRow ? stuffSearch.startRow : 1, + endRow: stuffSearch?.endRow ? stuffSearch.endRow : 100, + schSortType: stuffSearch?.schSortType ? stuffSearch.schSortType : 'R', + pageNo: stuffSearch?.pageNo, + pageSize: stuffSearch?.pageSize, + }) + } + } else if (stuffSearch.code === 'FINISH') { setStuffSearch({ - schObjectNo: objectNo ? objectNo : stuffSearch.schObjectNo, - schSaleStoreName: saleStoreName ? saleStoreName : '', - schAddress: address ? address : '', - schObjectName: objectName ? objectName : '', - schDispCompanyName: dispCompanyName ? dispCompanyName : '', - schSelSaleStoreId: stuffSearch?.schOtherSelSaleStoreId ? stuffSearch.schOtherSelSaleStoreId : stuffSearch.schSelSaleStoreId, - schReceiveUser: receiveUser ? receiveUser : '', + schObjectNo: objectNo, + schSaleStoreName: saleStoreName, + schAddress: address, + schObjectName: objectName, + schDispCompanyName: dispCompanyName, + schSelSaleStoreId: schSelSaleStoreId, + schOtherSelSaleStoreId: otherSaleStoreId, + schReceiveUser: receiveUser, schDateType: dateType, - schFromDt: dayjs(startDate).format('YYYY-MM-DD'), - schToDt: dayjs(endDate).format('YYYY-MM-DD'), + schFromDt: startDate ? dayjs(startDate).format('YYYY-MM-DD') : '', + schToDt: endDate ? dayjs(endDate).format('YYYY-MM-DD') : '', code: 'E', - startRow: stuffSearch?.startRow ? stuffSearch.startRow : 1, - endRow: stuffSearch?.endRow ? stuffSearch.endRow : 100, + startRow: 1, + endRow: 100, schSortType: stuffSearch?.schSortType ? stuffSearch.schSortType : 'R', }) + } else if (stuffSearch.code === 'E') { + if (session.storeId !== 'T01' && session.storeLvl === '1') { + setStuffSearch({ + schObjectNo: stuffSearch?.schObjectNo ? stuffSearch.schObjectNo : objectNo, + schSaleStoreName: stuffSearch?.schSaleStoreName ? stuffSearch.schSaleStoreName : saleStoreName, + schAddress: stuffSearch?.schAddress ? stuffSearch.schAddress : address, + schObjectName: stuffSearch?.schObjectName ? stuffSearch.schObjectName : objectName, + schDispCompanyName: stuffSearch?.schDispCompanyName ? stuffSearch.schDispCompanyName : dispCompanyName, + schSelSaleStoreId: otherSaleStoreId ? schSelSaleStoreId : '', + schOtherSelSaleStoreId: otherSaleStoreId, + schReceiveUser: stuffSearch?.schReceiveUser ? stuffSearch.schReceiveUser : receiveUser, + schDateType: dateType, + schFromDt: startDate ? dayjs(startDate).format('YYYY-MM-DD') : '', + schToDt: endDate ? dayjs(endDate).format('YYYY-MM-DD') : '', + code: 'E', + startRow: stuffSearch?.startRow ? stuffSearch.startRow : 1, + endRow: stuffSearch?.endRow ? stuffSearch.endRow : 100, + schSortType: stuffSearch?.schSortType ? stuffSearch.schSortType : 'R', + pageNo: stuffSearch?.pageNo, + pageSize: stuffSearch?.pageSize, + }) + } else if (session.storeId === 'T01') { + if (stuffSearch.pageNo !== 1) { + setStuffSearch({ + schObjectNo: objectNo ? objectNo : stuffSearch.schObjectNo, + schSaleStoreName: saleStoreName ? saleStoreName : '', + schAddress: address ? address : '', + schObjectName: objectName ? objectName : '', + schDispCompanyName: dispCompanyName ? dispCompanyName : '', + schSelSaleStoreId: stuffSearch?.schSelSaleStoreId ? stuffSearch.schSelSaleStoreId : '', + schOtherSelSaleStoreId: stuffSearch?.schOtherSelSaleStoreId ? stuffSearch.schOtherSelSaleStoreId : '', + schReceiveUser: receiveUser ? receiveUser : '', + schDateType: dateType, + schFromDt: startDate ? dayjs(startDate).format('YYYY-MM-DD') : '', + schToDt: endDate ? dayjs(endDate).format('YYYY-MM-DD') : '', + code: 'E', + startRow: 1, + endRow: 1 * stuffSearch?.pageSize, + // schSortType: stuffSearch?.schSortType ? stuffSearch.schSortType : 'R', + schSortType: 'R', + pageNo: 1, + pageSize: stuffSearch?.pageSize, + }) + } else { + setStuffSearch({ + schObjectNo: stuffSearch?.schObjectNo ? stuffSearch.schObjectNo : objectNo, + schSaleStoreName: stuffSearch?.schSaleStoreName ? stuffSearch.schSaleStoreName : saleStoreName, + schAddress: stuffSearch?.schAddress ? stuffSearch.schAddress : address, + schObjectName: stuffSearch?.schObjectName ? stuffSearch.schObjectName : objectName, + schDispCompanyName: stuffSearch?.schDispCompanyName ? stuffSearch.schDispCompanyName : dispCompanyName, + schSelSaleStoreId: schSelSaleStoreId, + schOtherSelSaleStoreId: otherSaleStoreId, + schReceiveUser: stuffSearch?.schReceiveUser ? stuffSearch.schReceiveUser : receiveUser, + schDateType: dateType, + schFromDt: startDate ? dayjs(startDate).format('YYYY-MM-DD') : '', + schToDt: endDate ? dayjs(endDate).format('YYYY-MM-DD') : '', + code: 'E', + startRow: stuffSearch?.startRow ? stuffSearch.startRow : 1, + endRow: stuffSearch?.endRow ? stuffSearch.endRow : 100, + schSortType: stuffSearch?.schSortType ? stuffSearch.schSortType : 'R', + pageNo: stuffSearch?.pageNo, + pageSize: stuffSearch?.pageSize, + }) + } + } else { + setStuffSearch({ + schObjectNo: stuffSearch?.schObjectNo ? stuffSearch.schObjectNo : objectNo, + schSaleStoreName: stuffSearch?.schSaleStoreName ? stuffSearch.schSaleStoreName : saleStoreName, + schAddress: stuffSearch?.schAddress ? stuffSearch.schAddress : address, + schObjectName: stuffSearch?.schObjectName ? stuffSearch.schObjectName : objectName, + schDispCompanyName: stuffSearch?.schDispCompanyName ? stuffSearch.schDispCompanyName : dispCompanyName, + schSelSaleStoreId: schSelSaleStoreId, + schOtherSelSaleStoreId: otherSaleStoreId, + schReceiveUser: stuffSearch?.schReceiveUser ? stuffSearch.schReceiveUser : receiveUser, + schDateType: dateType, + schFromDt: startDate ? dayjs(startDate).format('YYYY-MM-DD') : '', + schToDt: endDate ? dayjs(endDate).format('YYYY-MM-DD') : '', + code: 'E', + startRow: stuffSearch?.startRow ? stuffSearch.startRow : 1, + endRow: stuffSearch?.endRow ? stuffSearch.endRow : 100, + schSortType: stuffSearch?.schSortType ? stuffSearch.schSortType : 'R', + pageNo: stuffSearch?.pageNo, + pageSize: stuffSearch?.pageSize, + }) + } } else { setStuffSearch({ schObjectNo: objectNo, @@ -97,15 +236,18 @@ export default function StuffSearchCondition() { schAddress: address, schObjectName: objectName, schDispCompanyName: dispCompanyName, - schSelSaleStoreId: stuffSearch?.schOtherSelSaleStoreId ? stuffSearch.schOtherSelSaleStoreId : stuffSearch.schSelSaleStoreId, + schSelSaleStoreId: schSelSaleStoreId, + schOtherSelSaleStoreId: otherSaleStoreId, schReceiveUser: receiveUser, schDateType: dateType, - schFromDt: dayjs(startDate).format('YYYY-MM-DD'), - schToDt: dayjs(endDate).format('YYYY-MM-DD'), + schFromDt: startDate ? dayjs(startDate).format('YYYY-MM-DD') : '', + schToDt: endDate ? dayjs(endDate).format('YYYY-MM-DD') : '', code: 'E', - startRow: 1, - endRow: 100, + startRow: stuffSearch?.startRow ? stuffSearch.startRow : 1, + endRow: stuffSearch?.endRow ? stuffSearch.endRow : 100, schSortType: stuffSearch?.schSortType ? stuffSearch.schSortType : 'R', + pageNo: stuffSearch?.pageNo, + pageSize: stuffSearch?.pageSize, }) } } @@ -119,7 +261,7 @@ export default function StuffSearchCondition() { objectNameRef.current.value = '' dispCompanyNameRef.current.value = '' receiveUserRef.current.value = '' - + stuffSearch.schDateType = 'U' setObjectNo('') setAddress('') setobjectName('') @@ -131,12 +273,25 @@ export default function StuffSearchCondition() { setEndDate(dayjs(new Date()).format('YYYY-MM-DD')) if (session?.storeId === 'T01') { setSchSelSaleStoreId('') + setOtherSaleStoreId('') handleClear1() //판매대리점선택 자동완성 클리어 resetStuffRecoil() setStuffSearch({ - ...stuffSearch, + schObjectNo: '', + schAddress: '', + schObjectName: '', + schSaleStoreName: '', + schReceiveUser: '', + schDispCompanyName: '', schSelSaleStoreId: '', schOtherSelSaleStoreId: '', + schDateType: 'U', + startRow: 1, + endRow: 100, + schSortType: 'R', + pageNo: 1, + pageSize: 100, + code: 'S', }) } else { if (otherSaleStoreList.length > 1) { @@ -184,30 +339,24 @@ export default function StuffSearchCondition() { setSchSelSaleStoreList(allList) setFavoriteStoreList(favList) setShowSaleStoreList(favList) - // setSchSelSaleStoreId(session?.storeId) - setStuffSearch({ - ...stuffSearch, - code: 'S', - // schSelSaleStoreId: session?.storeId, - }) + if (stuffSearch.schSelSaleStoreId != '') { + setSchSelSaleStoreId(stuffSearch.schSelSaleStoreId) + url = `/api/object/saleStore/${stuffSearch.schSelSaleStoreId}/list?firstFlg=1&userId=${session?.userId}` + get({ url: url }).then((res) => { + if (!isEmptyArray(res)) { + res.map((row) => { + row.value = row.saleStoreId + row.label = row.saleStoreName + }) - //T01일때 2차 판매점 호출하기 디폴트로 1차점을 본인으로 셋팅해서 세션storeId사용 - // 디폴트 셋팅 안하기로 - // url = `/api/object/saleStore/${session?.storeId}/list?firstFlg=0&userId=${session?.userId}` - - // get({ url: url }).then((res) => { - // if (!isEmptyArray(res)) { - // res.map((row) => { - // row.value = row.saleStoreId - // row.label = row.saleStoreName - // }) - - // otherList = res - // setOtherSaleStoreList(otherList) - // } else { - // setOtherSaleStoreList([]) - // } - // }) + otherList = res.filter((row) => row.saleStoreLevel !== '1') + setOtherSaleStoreList(otherList) + setOtherSaleStoreId(stuffSearch.schOtherSelSaleStoreId) + } else { + setOtherSaleStoreList([]) + } + }) + } } else { if (session?.storeLvl === '1') { allList = res @@ -221,11 +370,9 @@ export default function StuffSearchCondition() { setOtherSaleStoreList(otherList) - setStuffSearch({ - ...stuffSearch, - code: 'S', - schSelSaleStoreId: allList[0].saleStoreId, - }) + if (stuffSearch.schOtherSelSaleStoreId != '') { + setOtherSaleStoreId(stuffSearch.schOtherSelSaleStoreId) + } } else { //10X22, 201X112 그냥2차점 //2차점인데 34들고있는애 202X217 @@ -241,7 +388,8 @@ export default function StuffSearchCondition() { setStuffSearch({ ...stuffSearch, code: 'S', - schSelSaleStoreId: otherList[0].saleStoreId, + schSelSaleStoreId: res[0].saleStoreId, + schOtherSelSaleStoreId: otherList[0].saleStoreId, }) } } @@ -267,7 +415,7 @@ export default function StuffSearchCondition() { const onInputChange = (key) => { if (key !== '') { setShowSaleStoreList(schSelSaleStoreList) - setOtherSaleStoreList([]) + // setOtherSaleStoreList([]) } else { setShowSaleStoreList(favoriteStoreList) } @@ -298,8 +446,13 @@ export default function StuffSearchCondition() { }) } else { //X누름 + //화면에선 지우는데 리코일은 조회누르지 않으면 보존 setSchSelSaleStoreId('') - stuffSearch.schSelSaleStoreId = '' + setOtherSaleStoreId('') + if (stuffSearch.code === 'S') { + stuffSearch.schSelSaleStoreId = '' + stuffSearch.schOtherSelSaleStoreId = '' + } //2차점 판매점목록비우기 setOtherSaleStoreList([]) @@ -311,24 +464,79 @@ export default function StuffSearchCondition() { if (isObjectNotEmpty(key)) { setOtherSaleStoreId(key.saleStoreId) stuffSearch.schOtherSelSaleStoreId = key.saleStoreId + + //2차점 골랐을때 1차점 값 + stuffSearch.schSelSaleStoreId = schSelSaleStoreId } else { //X누르면 검색조건에 1차점으로 셋팅 - setOtherSaleStoreId('') - setSchSelSaleStoreId(schSelSaleStoreId) - stuffSearch.schOtherSelSaleStoreId = '' - stuffSearch.schSelSaleStoreId = schSelSaleStoreId + + if (session.storeLvl === '1') { + if (stuffSearch.schOtherSelSaleStoreId === '') { + // 화면에선 지우는데 조회누르기 전이면 리코일은 남김 + setSchSelSaleStoreId(session.storeId) + } else { + // 화면에선 지우는데 조회누르기 전이면 리코일은 남김 + setOtherSaleStoreId('') + if (stuffSearch.code === 'S') { + stuffSearch.schOtherSelSaleStoreId = '' + } + } + } else { + setOtherSaleStoreId('') + setSchSelSaleStoreId(schSelSaleStoreId) + stuffSearch.schOtherSelSaleStoreId = '' + stuffSearch.schSelSaleStoreId = schSelSaleStoreId + } } } useEffect(() => { - setStartDate(stuffSearch?.schFromDt ? stuffSearch.schFromDt : dayjs(new Date()).add(-1, 'year').format('YYYY-MM-DD')) - setEndDate(stuffSearch?.schToDt ? stuffSearch.schToDt : dayjs(new Date()).format('YYYY-MM-DD')) - setObjectNo(stuffSearch.schObjectNo ? stuffSearch.schObjectNo : objectNo) - setSaleStoreName(stuffSearch.schSaleStoreName ? stuffSearch.schSaleStoreName : saleStoreName) - setAddress(stuffSearch.schAddress ? stuffSearch.schAddress : address) - setobjectName(stuffSearch.schObjectName ? stuffSearch.schObjectName : objectName) - setDispCompanyName(stuffSearch.schDispCompanyName ? stuffSearch.schDispCompanyName : dispCompanyName) - setReceiveUser(stuffSearch.schReceiveUser ? stuffSearch.schReceiveUser : receiveUser) + //X42 + if (session?.storeId === 'T01') { + if (stuffSearch.code === 'DELETE') { + setObjectNo('') + setSaleStoreName('') + setAddress('') + setobjectName('') + setDispCompanyName('') + setReceiveUser('') + objectNoRef.current.value = '' + saleStoreNameRef.current.value = '' + addressRef.current.value = '' + objectNameRef.current.value = '' + dispCompanyNameRef.current.value = '' + receiveUserRef.current.value = '' + stuffSearch.schObjectNo = '' + stuffSearch.schAddress = '' + stuffSearch.schObjectName = '' + stuffSearch.schSaleStoreName = '' + stuffSearch.schReceiveUser = '' + stuffSearch.schDispCompanyName = '' + stuffSearch.schDateType = 'U' + stuffSearch.schFromDt = dayjs(new Date()).add(-1, 'year').format('YYYY-MM-DD') + stuffSearch.schToDt = dayjs(new Date()).format('YYYY-MM-DD') + stuffSearch.startRow = 1 + stuffSearch.endRow = 100 + stuffSearch.schSelSaleStoreId = '' + stuffSearch.schOtherSelSaleStoreId = '' + stuffSearch.schSortType = 'R' + stuffSearch.pageNo = 1 + stuffSearch.pageSize = 100 + + setSchSelSaleStoreId('') + setOtherSaleStoreId('') + } + } else { + setStartDate(stuffSearch?.schFromDt ? stuffSearch.schFromDt : dayjs(new Date()).add(-1, 'year').format('YYYY-MM-DD')) + setEndDate(stuffSearch?.schToDt ? stuffSearch.schToDt : dayjs(new Date()).format('YYYY-MM-DD')) + setObjectNo(stuffSearch.schObjectNo ? stuffSearch.schObjectNo : objectNo) + setSaleStoreName(stuffSearch.schSaleStoreName ? stuffSearch.schSaleStoreName : saleStoreName) + setAddress(stuffSearch.schAddress ? stuffSearch.schAddress : address) + setobjectName(stuffSearch.schObjectName ? stuffSearch.schObjectName : objectName) + setDispCompanyName(stuffSearch.schDispCompanyName ? stuffSearch.schDispCompanyName : dispCompanyName) + setReceiveUser(stuffSearch.schReceiveUser ? stuffSearch.schReceiveUser : receiveUser) + setDateType(stuffSearch.schDateType ? stuffSearch.schDateType : dateType) + } }, [stuffSearch]) useEffect(() => { @@ -358,14 +566,14 @@ export default function StuffSearchCondition() {
@@ -392,6 +600,7 @@ export default function StuffSearchCondition() { className="input-light" defaultValue={stuffSearch?.schObjectNo ? stuffSearch.schObjectNo : objectNo} onChange={() => { + stuffSearch.schObjectNo = objectNoRef.current.value setObjectNo(objectNoRef.current.value) }} onKeyUp={handleByOnKeyUp} @@ -407,6 +616,7 @@ export default function StuffSearchCondition() { className="input-light" defaultValue={stuffSearch?.schSaleStoreName ? stuffSearch.schSaleStoreName : saleStoreName} onChange={() => { + stuffSearch.schSaleStoreName = saleStoreNameRef.current.value setSaleStoreName(saleStoreNameRef.current.value) }} onKeyUp={handleByOnKeyUp} @@ -422,6 +632,7 @@ export default function StuffSearchCondition() { className="input-light" defaultValue={stuffSearch?.schAddress ? stuffSearch.schAddress : address} onChange={() => { + stuffSearch.schAddress = addressRef.current.value setAddress(addressRef.current.value) }} onKeyUp={handleByOnKeyUp} @@ -437,6 +648,7 @@ export default function StuffSearchCondition() { className="input-light" defaultValue={stuffSearch?.schDispCompanyName ? stuffSearch.schDispCompanyName : dispCompanyName} onChange={() => { + stuffSearch.schDispCompanyName = dispCompanyNameRef.current.value setDispCompanyName(dispCompanyNameRef.current.value) }} onKeyUp={handleByOnKeyUp} @@ -454,6 +666,7 @@ export default function StuffSearchCondition() { className="input-light" defaultValue={stuffSearch?.schObjectName ? stuffSearch.schObjectName : objectName} onChange={() => { + stuffSearch.schObjectName = objectNameRef.current.value setobjectName(objectNameRef.current.value) }} onKeyUp={handleByOnKeyUp} @@ -469,6 +682,7 @@ export default function StuffSearchCondition() { ref={receiveUserRef} defaultValue={stuffSearch?.schReceiveUser ? stuffSearch.schReceiveUser : receiveUser} onChange={() => { + stuffSearch.schReceiveUser = receiveUserRef.current.value setReceiveUser(receiveUserRef.current.value) }} onKeyUp={handleByOnKeyUp} @@ -500,7 +714,9 @@ export default function StuffSearchCondition() { } else if (stuffSearch?.code === 'E' && schSelSaleStoreId !== '') { return option.saleStoreId === schSelSaleStoreId } else { - if (stuffSearch?.schSelSaleStoreId !== '') { + if (stuffSearch?.code === 'FINISH') { + return option.saleStoreId === schSelSaleStoreId + } else if (stuffSearch?.schSelSaleStoreId !== '') { return option.saleStoreId === stuffSearch.schSelSaleStoreId } else { return false @@ -531,7 +747,9 @@ export default function StuffSearchCondition() { } else if (stuffSearch?.code === 'E' && schSelSaleStoreId !== '') { return option.saleStoreId === schSelSaleStoreId } else { - if (stuffSearch?.schSelSaleStoreId !== '') { + if (stuffSearch?.code === 'FINISH') { + return option.saleStoreId === schSelSaleStoreId + } else if (stuffSearch?.schSelSaleStoreId !== '') { return option.saleStoreId === stuffSearch.schSelSaleStoreId } else { return false @@ -606,10 +824,11 @@ export default function StuffSearchCondition() { type="radio" name="radio_ptype" id="radio_u" - checked={dateType === 'U' ? true : false} + defaultChecked={stuffSearch.schDateType === 'U' ? true : false} value={'U'} onChange={(e) => { setDateType(e.target.value) + stuffSearch.schDateType = e.target.value }} /> @@ -619,10 +838,11 @@ export default function StuffSearchCondition() { type="radio" name="radio_ptype" id="radio_r" - checked={dateType === 'R' ? true : false} + defaultChecked={stuffSearch.schDateType === 'R' ? true : false} value={'R'} onChange={(e) => { setDateType(e.target.value) + stuffSearch.schDateType = e.target.value }} /> diff --git a/src/components/management/StuffSubHeader.jsx b/src/components/management/StuffSubHeader.jsx index 69c7b967..1ea2a699 100644 --- a/src/components/management/StuffSubHeader.jsx +++ b/src/components/management/StuffSubHeader.jsx @@ -27,10 +27,10 @@ export default function StuffSubHeader({ type }) { const param = { pid: '1', + objectNo: objectNo, } - //확인필요 const url = `/floor-plan?${queryStringFormatter(param)}` - console.log(url) + router.push(url) } @@ -107,7 +107,7 @@ export default function StuffSubHeader({ type }) { {getMessage('header.menus.management')}
  • - {getMessage('header.menus.management.newStuff')} + {getMessage('header.menus.management.detail')}
  • diff --git a/src/components/management/popup/PlanRequestPop.jsx b/src/components/management/popup/PlanRequestPop.jsx index bbe0f6c9..291549c6 100644 --- a/src/components/management/popup/PlanRequestPop.jsx +++ b/src/components/management/popup/PlanRequestPop.jsx @@ -98,8 +98,8 @@ export default function PlanRequestPop(props) { schPlanReqName: schPlanReqName, schPlanStatCd: schPlanStatCd, schDateGbn: schDateGbn, - schStartDt: dayjs(startDate).format('YYYY-MM-DD'), - schEndDt: dayjs(endDate).format('YYYY-MM-DD'), + schStartDt: startDate ? dayjs(startDate).format('YYYY-MM-DD') : '', + schEndDt: endDate ? dayjs(endDate).format('YYYY-MM-DD') : '', startRow: type === 'S' ? (1 - 1) * pageSize + 1 : (page - 1) * pageSize + 1, endRow: type === 'S' ? 1 * pageSize : page * pageSize, } @@ -226,7 +226,7 @@ export default function PlanRequestPop(props) { ], }) - //설계의뢰 그리드에서 선택한 설계의로 정보 + //설계의뢰 그리드에서 선택한 설계의뢰 정보 const getSelectedRowdata = (data) => { if (isNotEmptyArray(data)) { setPlanReqObject(data[0]) @@ -257,6 +257,16 @@ export default function PlanRequestPop(props) { const handleKeyUp = (e) => { let input = e.target input.value = input.value.replace(/[^0-9]/g, '') + if (e.key === 'Enter') { + onSubmit(pageNo, 'S') + } + } + + // 엔터 이벤트 + const handleByOnKeyUp = (e) => { + if (e.key === 'Enter') { + onSubmit(pageNo, 'S') + } } return ( @@ -324,6 +334,7 @@ export default function PlanRequestPop(props) { onChange={(e) => { setSchTitle(e.target.value) }} + onKeyUp={handleByOnKeyUp} />
    @@ -337,6 +348,7 @@ export default function PlanRequestPop(props) { onChange={(e) => { setSchAddress(e.target.value) }} + onKeyUp={handleByOnKeyUp} />
    @@ -352,6 +364,7 @@ export default function PlanRequestPop(props) { onChange={(e) => { setSchSaleStoreName(e.target.value) }} + onKeyUp={handleByOnKeyUp} /> @@ -365,6 +378,7 @@ export default function PlanRequestPop(props) { onChange={(e) => { setSchPlanReqName(e.target.value) }} + onKeyUp={handleByOnKeyUp} /> @@ -440,7 +454,7 @@ export default function PlanRequestPop(props) {
    Plan List
    - diff --git a/src/components/simulator/Simulator.jsx b/src/components/simulator/Simulator.jsx index c909ca17..fcef3a08 100644 --- a/src/components/simulator/Simulator.jsx +++ b/src/components/simulator/Simulator.jsx @@ -5,8 +5,9 @@ import { Bar } from 'react-chartjs-2' import dayjs from 'dayjs' import { useEffect, useState, useRef } from 'react' -import { useRecoilValue } from 'recoil' +import { useRecoilValue, useRecoilState } from 'recoil' import { floorPlanObjectState } from '@/store/floorPlanObjectAtom' +import { pwrGnrSimTypeState } from '@/store/simulatorAtom' import { useAxios } from '@/hooks/useAxios' import { useMessage } from '@/hooks/useMessage' @@ -41,6 +42,7 @@ export default function Simulator() { // 차트 관련 const [chartData, setChartData] = useState([]) + const data = { labels: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], datasets: [ @@ -105,8 +107,10 @@ export default function Simulator() { useEffect(() => { if (objectNo) { fetchObjectDetail(objectNo) + fetchSimulatorNotice() + setPwrGnrSimType('D') + setPwrRecoil({ ...pwrRecoil, type: 'D' }) } - fetchSimulatorNotice() }, [objectNo, plan]) // 물건 상세 정보 조회 @@ -118,13 +122,30 @@ export default function Simulator() { // 파워컨디셔너 조회 const [pcsInfoList, setPcsInfoList] = useState([]) + // 타입별 list 조회 + const [hatsudenryouAll, setHatsudenryouAll] = useState([]) + const [hatsudenryouAllSnow, setHatsudenryouAllSnow] = useState([]) + const [hatsudenryouPeakcutAll, setHatsudenryouPeakcutAll] = useState([]) + const [hatsudenryouPeakcutAllSnow, setHatsudenryouPeakcutAllSnow] = useState([]) + const fetchObjectDetail = async (objectNo) => { const apiUrl = `/api/pwrGnrSimulation/calculations?objectNo=${objectNo}&planNo=${plan?.id}` + const resultData = await get({ url: apiUrl }) if (resultData) { setObjectDetail(resultData) - if (resultData.frcPwrGnrList) { - setChartData(resultData.frcPwrGnrList) + if (resultData.hatsudenryouAll) { + setHatsudenryouAll(resultData.hatsudenryouAll) + } + if (resultData.hatsudenryouAllSnow) { + setHatsudenryouAllSnow(resultData.hatsudenryouAllSnow) + } + if (resultData.hatsudenryouPeakcutAll) { + setHatsudenryouPeakcutAll(resultData.hatsudenryouPeakcutAll) + } + if (resultData.hatsudenryouPeakcutAllSnow) { + setHatsudenryouPeakcutAllSnow(resultData.hatsudenryouPeakcutAllSnow) + setChartData(resultData.hatsudenryouPeakcutAllSnow) } if (resultData.pcsList) { setPcsInfoList(resultData.pcsList) @@ -148,6 +169,28 @@ export default function Simulator() { }) } + // 차트 데이터 변경 시, list type 셋팅 + const [pwrGnrSimType, setPwrGnrSimType] = useState('D') + const [pwrRecoil, setPwrRecoil] = useRecoilState(pwrGnrSimTypeState) + + const handleChartChangeData = (type) => { + setPwrRecoil({ ...pwrRecoil, type: type }) + switch (type) { + case 'A': + setChartData(hatsudenryouAll) + break + case 'B': + setChartData(hatsudenryouAllSnow) + break + case 'C': + setChartData(hatsudenryouPeakcutAll) + break + case 'D': + setChartData(hatsudenryouPeakcutAllSnow) + break + } + } + return (
    @@ -158,23 +201,25 @@ export default function Simulator() {
    {getMessage('simulator.title.sub1')}
    - {objectDetail.objectNo} (Plan No: {objectDetail.planNo}) + {objectDetail.objectNo} (Plan No: {plan?.id})
    {/* 작성일 */}
    {getMessage('simulator.title.sub2')}
    -
    {`${dayjs(objectDetail.drawingEstimateCreateDate).format('YYYY.MM.DD')}`}
    +
    + {objectDetail.drawingEstimateCreateDate ? `${dayjs(objectDetail.drawingEstimateCreateDate).format('YYYY.MM.DD')}` : ''} +
    {/* 시스템용량 */}
    {getMessage('simulator.title.sub3')}
    -
    {convertNumberToPriceDecimal(objectDetail.capacity)}kW
    +
    {objectDetail.capacity ? `${convertNumberToPriceDecimal(objectDetail.capacity)}kW` : ''}
    {/* 연간예측발전량 */}
    {getMessage('simulator.title.sub4')}
    -
    {convertNumberToPriceDecimal(objectDetail.anlFrcsGnrt)}
    +
    {chartData[chartData.length - 1]}
    @@ -206,6 +251,24 @@ export default function Simulator() {
    + {/* chart */} +
    + +
    @@ -237,7 +300,7 @@ export default function Simulator() { {chartData.length > 0 ? ( {chartData.map((data) => ( - {convertNumberToPriceDecimal(data)} + {data} ))} ) : ( diff --git a/src/hooks/common/useCanvasConfigInitialize.js b/src/hooks/common/useCanvasConfigInitialize.js index a07d4a00..18d1a052 100644 --- a/src/hooks/common/useCanvasConfigInitialize.js +++ b/src/hooks/common/useCanvasConfigInitialize.js @@ -40,19 +40,19 @@ export function useCanvasConfigInitialize() { const flowTexts = canvas.getObjects().filter((obj) => obj.name === 'flowText') if (basicSetting.roofAngleSet === 'slope') { offsetTexts.forEach((obj) => { - obj.set({ text: `${obj.originText}-∠${obj.pitch}${angleUnit}` }) + obj.set({ text: `${!obj.originText ? '' : obj.originText + '-'}∠${obj.pitch}${angleUnit}` }) }) flowTexts.forEach((obj) => { - obj.set({ text: `${obj.originText}-∠${obj.pitch}${pitchText}` }) + obj.set({ text: `${!obj.originText ? '' : obj.originText + '-'}∠${obj.pitch}${pitchText}` }) }) } if (basicSetting.roofAngleSet === 'flat') { offsetTexts.forEach((obj) => { - obj.set({ text: `${obj.originText}-∠${getDegreeByChon(obj.pitch)}${angleUnit}` }) + obj.set({ text: `${!obj.originText ? '' : obj.originText + '-'}∠${getDegreeByChon(obj.pitch)}${angleUnit}` }) }) flowTexts.forEach((obj) => { - obj.set({ text: `${obj.originText}-∠${getDegreeByChon(obj.pitch)}${pitchText}` }) + obj.set({ text: `${!obj.originText ? '' : obj.originText + '-'}∠${getDegreeByChon(obj.pitch)}${pitchText}` }) }) } diff --git a/src/hooks/common/useCanvasMenu.js b/src/hooks/common/useCanvasMenu.js index f8758e3e..7a883e55 100644 --- a/src/hooks/common/useCanvasMenu.js +++ b/src/hooks/common/useCanvasMenu.js @@ -1,8 +1,30 @@ import { menuNumberState } from '@/store/menuAtom' -import { useRecoilState } from 'recoil' +import { useRecoilState, useRecoilValue } from 'recoil' +import { useEffect } from 'react' +import { canvasState } from '@/store/canvasAtom' +import { usePolygon } from '@/hooks/usePolygon' +import { POLYGON_TYPE } from '@/common/common' export const useCanvasMenu = () => { const [menuNumber, setMenuNumber] = useRecoilState(menuNumberState) + const canvas = useRecoilValue(canvasState) + const { drawDirectionArrow } = usePolygon() + + useEffect(() => { + /* + * 모듈,회로 구성을 벗어나면 방향 표시 초기화 필요 + * */ + if (!canvas) return + if (![4, 5].includes(menuNumber)) { + canvas + .getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.ROOF) + .forEach((obj) => { + obj.set('moduleCompass', null) + drawDirectionArrow(obj) + }) + } + }, [menuNumber]) return { menuNumber, diff --git a/src/hooks/common/useCommonUtils.js b/src/hooks/common/useCommonUtils.js index 2902e0c3..1f9c2063 100644 --- a/src/hooks/common/useCommonUtils.js +++ b/src/hooks/common/useCommonUtils.js @@ -1,21 +1,20 @@ import { useEffect } from 'react' -import { useRecoilValue, useRecoilState } from 'recoil' +import { useRecoilState, useRecoilValue } from 'recoil' import { wordDisplaySelector } from '@/store/settingAtom' import { useEvent } from '@/hooks/useEvent' import { checkLineOrientation, getDistance } from '@/util/canvas-util' -import { dimensionLineSettingsState } from '@/store/commonUtilsAtom' +import { commonUtilsState, dimensionLineSettingsState } from '@/store/commonUtilsAtom' import { fontSelector } from '@/store/fontAtom' import { canvasState } from '@/store/canvasAtom' import { v4 as uuidv4 } from 'uuid' import { usePopup } from '@/hooks/usePopup' import Distance from '@/components/floor-plan/modal/distance/Distance' -import { commonUtilsState } from '@/store/commonUtilsAtom' -import { center, point } from '@turf/turf' export function useCommonUtils() { const canvas = useRecoilValue(canvasState) const wordDisplay = useRecoilValue(wordDisplaySelector) const { addCanvasMouseEventListener, addDocumentEventListener, initEvent } = useEvent() + // const { addCanvasMouseEventListener, addDocumentEventListener, initEvent } = useContext(EventContext) const dimensionSettings = useRecoilValue(dimensionLineSettingsState) const dimensionLineTextFont = useRecoilValue(fontSelector('dimensionLineText')) const commonTextFont = useRecoilValue(fontSelector('commonText')) @@ -23,7 +22,7 @@ export function useCommonUtils() { const { addPopup } = usePopup() useEffect(() => { - initEvent() + // initEvent() if (commonUtils.text) { commonTextMode() } else if (commonUtils.dimension) { @@ -39,6 +38,7 @@ export function useCommonUtils() { commonTextKeyEvent() addCanvasMouseEventListener('mouse:down', (event) => { const pointer = canvas?.getPointer(event.e) + textbox = new fabric.Textbox('', { left: pointer.x, top: pointer.y, @@ -49,7 +49,8 @@ export function useCommonUtils() { fill: commonTextFont.fontColor.value, fontFamily: commonTextFont.fontFamily.value, fontSize: commonTextFont.fontSize.value, - fontStyle: commonTextFont.fontWeight.value, + fontStyle: commonTextFont.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', + fontWeight: commonTextFont.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', selectable: true, lockMovementX: true, lockMovementY: true, @@ -262,7 +263,8 @@ export function useCommonUtils() { fill: dimensionLineTextFont.fontColor.value, fontSize: dimensionLineTextFont.fontSize.value, fontFamily: dimensionLineTextFont.fontFamily.value, - fontStyle: dimensionLineTextFont.fontWeight.value, + fontStyle: dimensionLineTextFont.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', + fontWeight: dimensionLineTextFont.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', selectable: true, textAlign: 'center', originX: 'center', @@ -710,7 +712,7 @@ export function useCommonUtils() { canvas?.remove(centerLine, ...extendLine, ...arrows, textObj) const reGroup = new fabric.Group(reGroupObj, { - name: 'dimensionLine', + name: 'dimensionGroup', selectable: true, lineDirection: originLineDirection, groupId: id, diff --git a/src/hooks/common/useFont.js b/src/hooks/common/useFont.js index 595ec74c..cb305565 100644 --- a/src/hooks/common/useFont.js +++ b/src/hooks/common/useFont.js @@ -12,13 +12,13 @@ export function useFont() { const circuitNumberText = useRecoilValue(fontSelector('circuitNumberText')) useEffect(() => { - if (canvas) { + if (canvas && commonText.fontWeight.value) { const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'commonText') textObjs.forEach((obj) => { obj.set({ fontFamily: commonText.fontFamily.value, - fontWeight: lengthText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', - fontStyle: lengthText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', + fontWeight: commonText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', + fontStyle: commonText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', fontSize: commonText.fontSize.value, fill: commonText.fontColor.value, }) @@ -28,13 +28,13 @@ export function useFont() { }, [commonText]) useEffect(() => { - if (canvas) { + if (canvas && dimensionLineText.fontWeight.value) { const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'dimensionLineText') textObjs.forEach((obj) => { obj.set({ fontFamily: dimensionLineText.fontFamily.value, - fontWeight: lengthText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', - fontStyle: lengthText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', + fontWeight: dimensionLineText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', + fontStyle: dimensionLineText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', fontSize: dimensionLineText.fontSize.value, fill: dimensionLineText.fontColor.value, }) @@ -44,13 +44,13 @@ export function useFont() { }, [dimensionLineText]) useEffect(() => { - if (canvas) { - const textObjs = canvas.getObjects().filter((obj) => obj.name === 'flowText') + if (canvas && flowText.fontWeight.value) { + const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'flowText') textObjs.forEach((obj) => { obj.set({ fontFamily: flowText.fontFamily.value, - fontWeight: lengthText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', - fontStyle: lengthText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', + fontWeight: flowText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', + fontStyle: flowText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', fontSize: flowText.fontSize.value, fill: flowText.fontColor.value, }) @@ -60,8 +60,8 @@ export function useFont() { }, [flowText]) useEffect(() => { - if (canvas) { - const textObjs = canvas.getObjects().filter((obj) => obj.name === 'lengthText') + if (canvas && lengthText.fontWeight.value) { + const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'lengthText') textObjs.forEach((obj) => { obj.set({ fontFamily: lengthText.fontFamily.value, diff --git a/src/hooks/common/useRefFiles.js b/src/hooks/common/useRefFiles.js index a5d085cc..05f60afe 100644 --- a/src/hooks/common/useRefFiles.js +++ b/src/hooks/common/useRefFiles.js @@ -1,13 +1,13 @@ -import { useRef, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { useRecoilState } from 'recoil' import { v4 as uuidv4 } from 'uuid' import { useSwal } from '@/hooks/useSwal' -import { convertDwgToPng } from '@/lib/cadAction' import { useAxios } from '../useAxios' import { currentCanvasPlanState } from '@/store/canvasAtom' +import { convertDwgToPng, writeImageBuffer } from '@/lib/fileAction' -export default function useRefFiles() { +export function useRefFiles() { const converterUrl = process.env.NEXT_PUBLIC_CONVERTER_API_URL const [refImage, setRefImage] = useState(null) const [refFileMethod, setRefFileMethod] = useState('1') @@ -16,7 +16,7 @@ export default function useRefFiles() { const queryRef = useRef(null) const { swalFire } = useSwal() - const { get, promisePut } = useAxios() + const { get, promisePut, promisePost } = useAxios() // const { currentCanvasPlan, setCurrentCanvasPlan } = usePlan() /** @@ -25,7 +25,11 @@ export default function useRefFiles() { */ const handleRefFile = (file) => { setRefImage(file) - file.name.split('.').pop() === 'dwg' ? handleUploadRefFile(file) : () => {} + /** + * 파일 확장자가 dwg일 경우 변환하여 이미지로 저장 + * 파일 확장자가 이미지일 경우 이미지 저장 + */ + file.name.split('.').pop() === 'dwg' ? handleUploadConvertRefFile(file) : handleUploadImageRefFile(file) // handleUploadRefFile(file) } @@ -58,22 +62,29 @@ export default function useRefFiles() { } /** - * 현재 플랜이 변경되면 플랜 상태 저장 + * 이미지 파일 업로드 + * @param {*} file */ - // useEffect(() => { - // const handleCurrentPlan = async () => { - // await promisePut({ url: '/api/canvas-management/canvas-statuses', data: currentCanvasPlan }).then((res) => { - // console.log('🚀 ~ awaitpromisePut ~ res:', res) - // }) - // } - // handleCurrentPlan() - // }, [currentCanvasPlan]) + const handleUploadImageRefFile = async (file) => { + console.log('🚀 ~ handleUploadImageRefFile ~ file:', file) + const formData = new FormData() + formData.append('file', file) + + const response = await fetch('http://localhost:3000/api/image-upload', { + method: 'POST', + body: formData, + }) + + const result = await response.json() + console.log('🚀 ~ handleUploadImageRefFile ~ res:', result) + // writeImageBuffer(file) + } /** * RefFile이 캐드 도면 파일일 경우 변환하여 이미지로 저장 * @param {*} file */ - const handleUploadRefFile = async (file) => { + const handleUploadConvertRefFile = async (file) => { const formData = new FormData() formData.append('file', file) @@ -95,6 +106,19 @@ export default function useRefFiles() { setRefFileMethod(e.target.value) } + /** + * 현재 플랜이 변경되면 플랜 상태 저장 + */ + useEffect(() => { + console.log('🚀 ~ useRefFiles ~ currentCanvasPlan:', currentCanvasPlan) + // const handleCurrentPlan = async () => { + // await promisePut({ url: '/api/canvas-management/canvas-statuses', data: currentCanvasPlan }).then((res) => { + // console.log('🚀 ~ awaitpromisePut ~ res:', res) + // }) + // } + // handleCurrentPlan() + }, [currentCanvasPlan]) + return { refImage, queryRef, diff --git a/src/hooks/contextpopup/useFlowDirectionSetting.js b/src/hooks/contextpopup/useFlowDirectionSetting.js new file mode 100644 index 00000000..9e77aa4a --- /dev/null +++ b/src/hooks/contextpopup/useFlowDirectionSetting.js @@ -0,0 +1,30 @@ +import { canvasState } from '@/store/canvasAtom' +import { useRecoilValue } from 'recoil' +import { usePolygon } from '@/hooks/usePolygon' +import { useState } from 'react' +import { usePopup } from '@/hooks/usePopup' + +export const FLOW_DIRECTION_TYPE = { + EIGHT_AZIMUTH: 'eightAzimuth', + TWENTY_FOUR_AZIMUTH: 'twentyFourAzimuth', +} + +export function useFlowDirectionSetting(id) { + const canvas = useRecoilValue(canvasState) + const { drawDirectionArrow } = usePolygon() + const { closePopup } = usePopup() + + const [type, setType] = useState(FLOW_DIRECTION_TYPE.EIGHT_AZIMUTH) + + const changeSurfaceFlowDirection = (roof, direction, orientation) => { + roof.set({ + direction: direction, + surfaceCompass: orientation, + }) + drawDirectionArrow(roof) + canvas?.renderAll() + closePopup(id) + } + + return { changeSurfaceFlowDirection, type, setType } +} diff --git a/src/hooks/floorPlan/estimate/useEstimateController.js b/src/hooks/floorPlan/estimate/useEstimateController.js index 05d10554..ddfa2e68 100644 --- a/src/hooks/floorPlan/estimate/useEstimateController.js +++ b/src/hooks/floorPlan/estimate/useEstimateController.js @@ -6,34 +6,19 @@ import { estimateState, floorPlanObjectState } from '@/store/floorPlanObjectAtom import { isObjectNotEmpty } from '@/util/common-utils' import { SessionContext } from '@/app/SessionProvider' import { useMessage } from '@/hooks/useMessage' - -const reducer = (prevState, nextState) => { - return { ...prevState, ...nextState } -} +import { useRouter } from 'next/navigation' +import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider' // Constants -const ESTIMATE_API_ENDPOINT = '/api/estimates' // API 엔드포인트 정의 - -const defaultEstimateData = { - estimateDate: new Date(), //견적일 - charger: '', //담당자 - objectName: '', //안건명 - objectNameOmit: '', //경칭코드 - estimateType: 'YJOD', //주문분류 - remarks: '', //비고 - estimateOption: '', //견적특이사항 - itemList: [], - fileList: [], - fileFlg: '0', //후일 자료 제출 (체크 1 노체크 0) - priceCd: '', -} +const ESTIMATE_API_ENDPOINT = '/api/estimate' // API 엔드포인트 정의 // Helper functions -const updateItemInList = (itemList, itemId, updates) => { - return itemList.map((item) => (item.itemId === itemId ? { ...item, ...updates } : item)) +const updateItemInList = (itemList, dispOrder, updates) => { + return itemList.map((item) => (item.dispOrder === dispOrder ? { ...item, ...updates } : item)) } export const useEstimateController = (planNo) => { + const router = useRouter() const { session } = useContext(SessionContext) const globalLocaleState = useRecoilValue(globalLocaleStore) const objectRecoil = useRecoilValue(floorPlanObjectState) @@ -41,25 +26,32 @@ export const useEstimateController = (planNo) => { const { getMessage } = useMessage() - const { get, post, promisePost } = useAxios(globalLocaleState) + const { promiseGet, post, promisePost } = useAxios(globalLocaleState) const [isLoading, setIsLoading] = useState(false) - const [state, setState] = useReducer(reducer, defaultEstimateData) + const { estimateContextState, setEstimateContextState } = useContext(FloorPlanContext) useEffect(() => { - if (!isLoading) { + if (planNo && !isLoading) { if (objectRecoil.floorPlanObjectNo && planNo) { - fetchSetting() + fetchSetting(objectRecoil.floorPlanObjectNo, planNo) } } }, []) // 상세 조회 - const fetchSetting = async () => { + const fetchSetting = async (objectNo, planNo) => { try { - await get({ url: `/api/estimate/${objectRecoil.floorPlanObjectNo}/${planNo}/detail` }).then((res) => { - if (isObjectNotEmpty(res)) { - setState(res) + await promiseGet({ url: `/api/estimate/${objectNo}/${planNo}/detail` }).then((res) => { + if (res.status === 200) { + if (isObjectNotEmpty(res.data)) { + if (res.data.itemList.length > 0) { + res.data.itemList.map((item) => { + item.delFlg = '0' + }) + } + setEstimateContextState(res.data) + } } }) setIsLoading(true) @@ -69,95 +61,239 @@ export const useEstimateController = (planNo) => { } } - const updateItem = (itemId, updates) => { - setState({ - itemList: updateItemInList(state.itemList, itemId, updates), + const updateItem = (dispOrder, updates) => { + setEstimateContextState({ + itemList: updateItemInList(estimateContextState.itemList, dispOrder, updates), }) } const addItem = () => { - const newItemDispOrder = (Math.max(...state.itemList.map((item) => item.dispOrder)) + 1) * 100 - setState({ + let newItemDispOrder = Math.max(...estimateContextState.itemList.map((item) => item.dispOrder)) + newItemDispOrder = (Math.floor(newItemDispOrder / 100) + 1) * 100 + setEstimateContextState({ itemList: [ - ...state.itemList, + ...estimateContextState.itemList, { - dispOrder: newItemDispOrder, + objectNo: objectRecoil.floorPlanObjectNo, + planNo: planNo, + dispOrder: newItemDispOrder.toString(), itemId: '', //제품번호 - itemNo: '', //형명 - itemName: '', + itemNo: '', + itemName: '', //형명 amount: '', //수량 unitPrice: '0', unit: '', //단위 - salePrice: '0', //단가 - saleTotPrice: '0', //금액(부가세별도) + salePrice: '', //단가 + saleTotPrice: '', //금액(부가세별도) + itemChangeFlg: '1', //추가시 체인지플래그 1로 + partAdd: '1', //NEW 체인지 플래그 + delFlg: '0', //삭제 플래그 0 삭제하면 1 + addFlg: true, }, ], }) } useEffect(() => { - setEstimateData({ ...state, userId: session.userId, sapSalesStoreCd: session.custCd }) - }, [state]) + setEstimateData({ ...estimateContextState, userId: session.userId, sapSalesStoreCd: session.custCd }) + }, [estimateContextState]) + + // 첨부파일 다운로드 + const handleEstimateFileDownload = async (originFile) => { + const options = { responseType: 'blob' } + + await promisePost({ url: `/api/file/fileDownload`, data: originFile, option: options }) + .then((resultData) => { + if (resultData) { + const blob = new Blob([resultData.data], { type: resultData.headers['content-type'] || 'application/octet-stream' }) + const fileUrl = window.URL.createObjectURL(blob) + const link = document.createElement('a') + + link.href = fileUrl + link.download = originFile.faileName + document.body.appendChild(link) + link.click() + link.remove() + window.URL.revokeObjectURL(fileUrl) + } + }) + .catch((error) => { + console.log('::FileDownLoad Error::', error) + alert('File does not exist.') + }) + } //견적서 저장 const handleEstimateSubmit = async () => { //0. 필수체크 let flag = true - console.log('::담긴 estimateData:::', estimateData) + let fileFlg = true + let itemFlg = true + if (estimateData.charger.trim().length === 0) { + flag = false + return alert(getMessage('estimate.detail.save.requiredCharger')) + } + if (estimateData.objectName.trim().length === 0) { + flag = false + return alert(getMessage('estimate.detail.save.requiredObjectName')) + } + + if (isNaN(Date.parse(estimateData.estimateDate))) { + flag = false + return alert(getMessage('estimate.detail.save.requiredEstimateDate')) + } + + //첨부파일을 첨부안했는데 //아이템 fileUploadFlg가1(첨부파일 필수)이 1개라도 있는데 후일 자료 제출(fileFlg) 체크안했으면(0) alert 저장안돼 - if (estimateData.itemList.length > 1) { - estimateData.itemList.map((row) => { - if (row.fileUploadFlg === '1') { - if (estimateData.fileFlg === '0') { - alert(getMessage('estimate.detail.save.requiredMsg')) - flag = false + if (flag) { + if (estimateData.fileList.length < 1) { + if (estimateData.itemList.length > 1) { + estimateData.itemList.map((row) => { + if (row.delFlg === '0') { + if (row.fileUploadFlg === '1') { + if (fileFlg) { + if (estimateData.fileFlg === '0') { + fileFlg = false + return alert(getMessage('estimate.detail.save.requiredFileUpload')) + } + } + } + } + }) + } + } + } + + if (fileFlg) { + estimateData.itemList.map((item) => { + if (item.delFlg === '0') { + item.amount = item.amount?.replaceAll(',', '') + item.salePrice = parseFloat(item.salePrice?.replaceAll(',', '')).toFixed(2) + item.saleTotPrice = parseFloat(item.saleTotPrice?.replaceAll(',', '')).toFixed(2) + + if (!item.paDispOrder) { + if (itemFlg) { + if (isNaN(item.amount)) { + item.amount = '0' + } + + if (item.amount < 1) { + itemFlg = false + return alert(getMessage('estimate.detail.save.requiredAmount')) + } + + if (estimateData.estimateType !== 'YJSS') { + if (isNaN(item.salePrice)) { + item.salePrice = '0' + } + + if (item.salePrice < 1) { + itemFlg = false + return alert(getMessage('estimate.detail.save.requiredSalePrice')) + } + } + } } } }) } - if (flag) { - //1. 첨부파일 저장 + + if (flag && fileFlg && itemFlg) { + //1. 첨부파일 저장시작 const formData = new FormData() formData.append('file', estimateData.fileList) formData.append('objectNo', estimateData.objectNo) formData.append('planNo', estimateData.planNo) formData.append('category', '10') formData.append('userId', estimateData.userId) - // for (const value of formData.values()) { - // console.log('formData::', value) - // } await post({ url: '/api/file/fileUpload', data: formData }) + //첨부파일저장끝 - //2. 상세데이터 저장 - return - await promisePost({ url: `${ESTIMATE_API_ENDPOINT}/save-estimate`, data: estimateData }).then((res) => { - if (res) { - alert(getMessage('estimate.detail.save.alertMsg')) + //제품라인 추가했는데 아이템 안고르고 저장하면itemId=''은 날리고 나머지 저장하기 + estimateData.itemList = estimateData.itemList.filter((item) => item.itemId !== '') + + let delCnt = 0 + estimateData.itemList.map((item) => { + if (item.delFlg === '1') { + delCnt++ + } + }) + if (delCnt === estimateData.itemList.length) { + return alert(getMessage('estimate.detail.save.requiredItem')) + } + + // console.log('최종 아이템 정보::;', estimateData.itemList) + let option = [] + estimateData.itemList.forEach((item) => { + if (item.specialNoteCd) { + let split2 = item.specialNoteCd.split('、') + option = option.concat(split2) } }) - // try { - // const result = await promisePost({ - // url: ESTIMATE_API_ENDPOINT, - // data: estimateData, - // }) - // alert(getMessage('estimate.detail.save.alertMsg')) - // return result - // } catch (error) { - // console.error('Failed to submit estimate:', error) - // throw error - // } + // console.log('아이템리스트::', estimateData.itemList) + // console.log('최종 정보::;', estimateData) + //2. 상세데이터 저장 + // return + try { + await promisePost({ url: `${ESTIMATE_API_ENDPOINT}/save-estimate`, data: estimateData }).then((res) => { + if (res.status === 201) { + alert(getMessage('estimate.detail.save.alertMsg')) + //어디로 보낼지 + fetchSetting(objectRecoil.floorPlanObjectNo, estimateData.planNo) + } + }) + } catch (e) { + console.log('error::::::::::::', e.response.data.message) + } } } + /** + * 견적서 복사버튼 + * (견적서 번호(estimateData.docNo)가 생성된 이후 버튼 활성화 ) + * T01관리자 계정 및 1차판매점에게만 제공 + */ + const handleEstimateCopy = async (sendPlanNo, copyReceiveUser, saleStoreId, otherSaleStoreId) => { + if (saleStoreId === '') { + return alert(getMessage('estimate.detail.productFeaturesPopup.requiredStoreId')) + } + + if (copyReceiveUser.trim().length === 0) { + return alert(getMessage('estimate.detail.productFeaturesPopup.requiredReceiveUser')) + } + const params = { + saleStoreId: session.storeId, + sapSalesStoreCd: session.custCd, + objectNo: objectRecoil.floorPlanObjectNo, + planNo: sendPlanNo, + copySaleStoreId: otherSaleStoreId ? otherSaleStoreId : saleStoreId, + copyReceiveUser: copyReceiveUser, + userId: session.userId, + } + + // return + await promisePost({ url: '/api/estimate/save-estimate-copy', data: params }).then((res) => { + if (res.status === 201) { + if (isObjectNotEmpty(res.data)) { + let newObjectNo = res.data.objectNo + alert(getMessage('estimate.detail.estimateCopyPopup.copy.alertMessage')) + router.push(`/management/stuff/detail?objectNo=${newObjectNo.toString()}`, { scroll: false }) + } + } + }) + } + return { - state, - setState, + estimateContextState, + setEstimateContextState, updateItem, addItem, handleEstimateSubmit, fetchSetting, + handleEstimateFileDownload, + handleEstimateCopy, } } diff --git a/src/hooks/main/useMainContentsController.js b/src/hooks/main/useMainContentsController.js new file mode 100644 index 00000000..01f75658 --- /dev/null +++ b/src/hooks/main/useMainContentsController.js @@ -0,0 +1,94 @@ +'use client' + +import { useContext, useEffect } from 'react' +import { useAxios } from '../useAxios' +import { SessionContext } from '@/app/SessionProvider' +import { QcastContext } from '@/app/QcastProvider' + +export const useMainContentsController = () => { + const { session } = useContext(SessionContext) + const { promiseGet } = useAxios() + const { setQcastState, setIsGlobalLoading } = useContext(QcastContext) + + useEffect(() => { + setIsGlobalLoading(true) + }, []) + + /** + * main search area + */ + // const [saleStoreId, setSaleStoreId] = useState('') + // const [saleStoreName, setSaleStoreName] = useState('') + + /** + * main contents area + */ + // const [objectList, setObjectList] = useState([]) + // const [businessCharger, setBusinessCharger] = useState(null) + // const [businessChargerMail, setBusinessChargerMail] = useState(null) + + /** + * 최근 물건 목록 조회 + */ + const fetchObjectList = async () => { + try { + const apiUrl = `/api/main-page/object/${session?.storeId}/list` + await promiseGet({ + url: apiUrl, + }).then((res) => { + if (res.status === 200) { + // setSaleStoreId(res.data.saleStoreId) + // setSaleStoreName(res.data.saleStoreName) + + // setObjectList(res.data.objectList) + // setBusinessCharger(res.data.businessCharger) + // setBusinessChargerMail(res.data.businessChargerMail) + setQcastState({ + saleStoreId: res.data.saleStoreId, + saleStoreName: res.data.saleStoreName, + objectList: res.data.objectList, + businessCharger: res.data.businessCharger, + businessChargerMail: res.data.businessChargerMail, + }) + setIsGlobalLoading(false) + } else { + // setSaleStoreId('') + // setSaleStoreName('') + + // setObjectList([]) + // setBusinessCharger(null) + // setBusinessChargerMail(null) + setQcastState({ + saleStoreId: '', + saleStoreName: '', + objectList: [], + businessCharger: null, + businessChargerMail: null, + }) + } + }) + } catch (error) { + console.error('MAIN API fetching error:', error) + } + } + + const initObjectList = () => { + setQcastState({ + saleStoreId: '', + saleStoreName: '', + objectList: [], + businessCharger: null, + businessChargerMail: null, + }) + } + + return { + // saleStoreId, + // saleStoreName, + // objectList, + // businessCharger, + // businessChargerMail, + fetchObjectList, + initObjectList, + } +} diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js new file mode 100644 index 00000000..067d9f8a --- /dev/null +++ b/src/hooks/module/useModuleBasicSetting.js @@ -0,0 +1,963 @@ +import { useContext, useEffect, useState } from 'react' +import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' +import { canvasState } from '@/store/canvasAtom' +import { rectToPolygon, setSurfaceShapePattern } from '@/util/canvas-util' +import { roofDisplaySelector } from '@/store/settingAtom' +import offsetPolygon from '@/util/qpolygon-utils' +import { QPolygon } from '@/components/fabric/QPolygon' +import { QLine } from '@/components/fabric/QLine' +import { moduleSetupSurfaceState, moduleIsSetupState } from '@/store/canvasAtom' +import { useEvent } from '@/hooks/useEvent' +import { POLYGON_TYPE, BATCH_TYPE } from '@/common/common' + +import * as turf from '@turf/turf' +import { EventContext } from '@/app/floor-plan/EventProvider' + +export function useModuleBasicSetting() { + const canvas = useRecoilValue(canvasState) + const roofDisplay = useRecoilValue(roofDisplaySelector) + const [moduleSetupSurface, setModuleSetupSurface] = useRecoilState(moduleSetupSurfaceState) + const [moduleIsSetup, setModuleIsSetup] = useRecoilState(moduleIsSetupState) + const { addTargetMouseEventListener, addCanvasMouseEventListener, initEvent } = useEvent() + // const { addTargetMouseEventListener, addCanvasMouseEventListener, initEvent } = useContext(EventContext) + const [flowModuleLine, setFlowModuleLine] = useState({}) + let selectedModuleInstSurfaceArray = [] + + const makeModuleInstArea = () => { + //지붕 객체 반환 + const roofs = canvas.getObjects().filter((obj) => obj.name === 'roof') + + if (!roofs) { + return + } + + roofs.forEach((roof) => { + setSurfaceShapePattern(roof, roofDisplay.column, true) //패턴 변경 + const offsetPoints = offsetPolygon(roof.points, -20) //안쪽 offset + //모듈설치영역?? 생성 + let setupSurface = new QPolygon(offsetPoints, { + stroke: 'red', + fill: 'transparent', + strokeDashArray: [10, 4], + strokeWidth: 1, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + selectable: true, + parentId: roof.id, //가대 폴리곤의 임시 인덱스를 넣어줌 + name: POLYGON_TYPE.MODULE_SETUP_SURFACE, + flowDirection: roof.direction, + flipX: roof.flipX, + flipY: roof.flipY, + }) + + setupSurface.setViewLengthText(false) + + canvas.add(setupSurface) + + if (setupSurface.flowDirection === 'south' || setupSurface.flowDirection === 'north') { + setFlowModuleLine(bottomTopFlowLine(setupSurface)) + } else { + setFlowModuleLine(leftRightFlowLine(setupSurface)) + } + + //지붕면 선택 금지 + roof.set({ + selectable: false, + }) + + //모듈설치면 클릭이벤트 + addTargetMouseEventListener('mousedown', setupSurface, function () { + toggleSelection(setupSurface) + }) + }) + } + + //설치 범위 지정 클릭 이벤트 + const toggleSelection = (setupSurface) => { + const isExist = selectedModuleInstSurfaceArray.some((obj) => obj.parentId === setupSurface.parentId) + //최초 선택일때 + if (!isExist) { + //기본 선택이랑 스트로크 굵기가 같으면 선택 안됨으로 봄 + setupSurface.set({ + ...setupSurface, + strokeWidth: 3, + strokeDashArray: [0], + fill: 'transparent', + }) + canvas.discardActiveObject() // 객체의 활성 상태 해제 + //중복으로 들어가는걸 방지하기 위한 코드 + + canvas?.renderAll() + selectedModuleInstSurfaceArray.push(setupSurface) + } else { + //선택후 재선택하면 선택안됨으로 변경 + setupSurface.set({ + ...setupSurface, + fill: 'transparent', + strokeDashArray: [10, 4], + strokeWidth: 1, + }) + canvas.discardActiveObject() // 객체의 활성 상태 해제 + + //폴리곤에 커스텀 인덱스를 가지고 해당 배열 인덱스를 찾아 삭제함 + const removeIndex = setupSurface.parentId + const removeArrayIndex = selectedModuleInstSurfaceArray.findIndex((obj) => obj.parentId === removeIndex) + selectedModuleInstSurfaceArray.splice(removeArrayIndex, 1) + } + + canvas?.renderAll() + setModuleSetupSurface([...selectedModuleInstSurfaceArray]) + } + + /** + * trestle에서 영역을 가져와 mouse:move 이벤트로 해당 영역에 진입했을때 booleanPointInPolygon 로 진입여부를 확인 + * 확인 후 셀을 이동시킴 + */ + const manualModuleSetup = () => { + const moduleSetupSurfaces = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) //모듈설치면를 가져옴 + const batchObjects = canvas + ?.getObjects() + .filter( + (obj) => + obj.name === BATCH_TYPE.OPENING || + obj.name === BATCH_TYPE.TRIANGLE_DORMER || + obj.name === BATCH_TYPE.PENTAGON_DORMER || + obj.name === BATCH_TYPE.SHADOW, + ) //도머s 객체 + + if (moduleSetupSurfaces.length !== 0) { + let tempModule + let manualDrawModules = moduleIsSetup // 앞에서 자동으로 했을때 추가됨 + let inside = false + let turfPolygon + let flowDirection + let trestlePolygon + addCanvasMouseEventListener('mouse:move', (e) => { + //마우스 이벤트 삭제 후 재추가 + const mousePoint = canvas.getPointer(e.e) + + for (let i = 0; i < moduleSetupSurfaces.length; i++) { + turfPolygon = polygonToTurfPolygon(moduleSetupSurfaces[i]) + trestlePolygon = moduleSetupSurfaces[i] + flowDirection = moduleSetupSurfaces[i].flowDirection //도형의 방향 + let width = flowDirection === 'south' || flowDirection === 'north' ? 172 : 113 + let height = flowDirection === 'south' || flowDirection === 'north' ? 113 : 172 + + const points = [ + { x: mousePoint.x - width / 2, y: mousePoint.y - height / 2 }, + { x: mousePoint.x + width / 2, y: mousePoint.y - height / 2 }, + { x: mousePoint.x + width / 2, y: mousePoint.y + height / 2 }, + { x: mousePoint.x - width / 2, y: mousePoint.y + height / 2 }, + ] + + const turfPoints = coordToTurfPolygon(points) + + if (turf.booleanWithin(turfPoints, turfPolygon)) { + let isDrawing = false + + if (isDrawing) return + canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tempModule')) //움직일때 일단 지워가면서 움직임 + + tempModule = new fabric.Rect({ + fill: 'white', + stroke: 'black', + strokeWidth: 1, + width: width, + height: height, + left: mousePoint.x - width / 2, + top: mousePoint.y - height / 2, + selectable: false, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + opacity: 0.8, + name: 'tempModule', + parentId: moduleSetupSurfaces[i].parentId, + }) + + canvas?.add(tempModule) //움직여가면서 추가됨 + + /** + * 스냅기능 + */ + let snapDistance = 10 + let cellSnapDistance = 20 + + const trestleLeft = moduleSetupSurfaces[i].left + const trestleTop = moduleSetupSurfaces[i].top + const trestleRight = trestleLeft + moduleSetupSurfaces[i].width * moduleSetupSurfaces[i].scaleX + const trestleBottom = trestleTop + moduleSetupSurfaces[i].height * moduleSetupSurfaces[i].scaleY + const bigCenterY = (trestleTop + trestleTop + moduleSetupSurfaces[i].height) / 2 + + // 작은 폴리곤의 경계 좌표 계산 + const smallLeft = tempModule.left + const smallTop = tempModule.top + const smallRight = smallLeft + tempModule.width * tempModule.scaleX + const smallBottom = smallTop + tempModule.height * tempModule.scaleY + const smallCenterX = smallLeft + (tempModule.width * tempModule.scaleX) / 2 + const smallCenterY = smallTop + (tempModule.height * tempModule.scaleX) / 2 + + /** + * 미리 깔아놓은 셀이 있을때 셀에 흡착됨 + */ + if (manualDrawModules) { + manualDrawModules.forEach((cell) => { + const holdCellLeft = cell.left + const holdCellTop = cell.top + const holdCellRight = holdCellLeft + cell.width * cell.scaleX + const holdCellBottom = holdCellTop + cell.height * cell.scaleY + const holdCellCenterX = holdCellLeft + (cell.width * cell.scaleX) / 2 + const holdCellCenterY = holdCellTop + (cell.height * cell.scaleY) / 2 + + //설치된 셀에 좌측에 스냅 + if (Math.abs(smallRight - holdCellLeft) < snapDistance) { + tempModule.left = holdCellLeft - width - 0.5 + } + + //설치된 셀에 우측에 스냅 + if (Math.abs(smallLeft - holdCellRight) < snapDistance) { + tempModule.left = holdCellRight + 0.5 + } + + //설치된 셀에 위쪽에 스냅 + if (Math.abs(smallBottom - holdCellTop) < snapDistance) { + tempModule.top = holdCellTop - height - 0.5 + } + + //설치된 셀에 밑쪽에 스냅 + if (Math.abs(smallTop - holdCellBottom) < snapDistance) { + tempModule.top = holdCellBottom + 0.5 + } + //가운데 -> 가운데 + if (Math.abs(smallCenterX - holdCellCenterX) < cellSnapDistance) { + tempModule.left = holdCellCenterX - width / 2 + } + //왼쪽 -> 가운데 + if (Math.abs(smallLeft - holdCellCenterX) < cellSnapDistance) { + tempModule.left = holdCellCenterX + } + // 오른쪽 -> 가운데 + if (Math.abs(smallRight - holdCellCenterX) < cellSnapDistance) { + tempModule.left = holdCellCenterX - width + } + //세로 가운데 -> 가운데 + if (Math.abs(smallCenterY - holdCellCenterY) < cellSnapDistance) { + tempModule.top = holdCellCenterY - height / 2 + } + //위쪽 -> 가운데 + if (Math.abs(smallTop - holdCellCenterY) < cellSnapDistance) { + tempModule.top = holdCellCenterY + } + //아랫쪽 -> 가운데 + if (Math.abs(smallBottom - holdCellCenterY) < cellSnapDistance) { + tempModule.top = holdCellCenterY - height + } + }) + } + + // 위쪽 변에 스냅 + if (Math.abs(smallTop - trestleTop) < snapDistance) { + tempModule.top = trestleTop + } + + // 아래쪽 변에 스냅 + if (Math.abs(smallTop + tempModule.height * tempModule.scaleY - (trestleTop + moduleSetupSurfaces[i].height)) < snapDistance) { + tempModule.top = trestleTop + moduleSetupSurfaces[i].height - tempModule.height * tempModule.scaleY + } + + // 왼쪽변에 스냅 + if (Math.abs(smallLeft - trestleLeft) < snapDistance) { + tempModule.left = trestleLeft + } + //오른쪽 변에 스냅 + if (Math.abs(smallRight - trestleRight) < snapDistance) { + tempModule.left = trestleRight - tempModule.width * tempModule.scaleX + } + + if (flowDirection === 'south' || flowDirection === 'north') { + // 모듈왼쪽이 세로중앙선에 붙게 스냅 + if (Math.abs(smallLeft - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < snapDistance) { + tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 + } + + // 모듈이 가운데가 세로중앙선에 붙게 스냅 + if (Math.abs(smallCenterX - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < snapDistance) { + tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 - (tempModule.width * tempModule.scaleX) / 2 + } + + // 모듈오른쪽이 세로중앙선에 붙게 스냅 + if (Math.abs(smallRight - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < snapDistance) { + tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 - tempModule.width * tempModule.scaleX + } + } else { + // 모듈이 가로중앙선에 스냅 + if (Math.abs(smallTop + tempModule.height / 2 - bigCenterY) < snapDistance) { + tempModule.top = bigCenterY - tempModule.height / 2 + } + + if (Math.abs(smallTop - (trestleTop + moduleSetupSurfaces[i].height / 2)) < snapDistance) { + tempModule.top = trestleTop + moduleSetupSurfaces[i].height / 2 + } + // 모듈 밑면이 가로중앙선에 스냅 + if (Math.abs(smallBottom - (trestleTop + moduleSetupSurfaces[i].height / 2)) < snapDistance) { + tempModule.top = trestleTop + moduleSetupSurfaces[i].height / 2 - tempModule.height * tempModule.scaleY + } + } + + tempModule.setCoords() + canvas?.renderAll() + inside = true + break + } else { + inside = false + } + } + + if (!inside) { + canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tempModule')) + canvas?.renderAll() + } + }) + + addCanvasMouseEventListener('mouse:up', (e) => { + let isIntersection = true + if (!inside) return + if (tempModule) { + const rectPoints = [ + { x: tempModule.left + 0.5, y: tempModule.top + 0.5 }, + { x: tempModule.left + 0.5 + tempModule.width * tempModule.scaleX, y: tempModule.top + 0.5 }, + { + x: tempModule.left + tempModule.width * tempModule.scaleX + 0.5, + y: tempModule.top + tempModule.height * tempModule.scaleY + 0.5, + }, + { x: tempModule.left + 0.5, y: tempModule.top + tempModule.height * tempModule.scaleY + 0.5 }, + ] + + tempModule.set({ points: rectPoints }) + const tempTurfModule = polygonToTurfPolygon(tempModule) + + //도머 객체를 가져옴 + if (batchObjects) { + batchObjects.forEach((object) => { + let dormerTurfPolygon + + if (object.type === 'group') { + //도머는 그룹형태임 + dormerTurfPolygon = batchObjectGroupToTurfPolygon(object) + } else { + //개구, 그림자 + dormerTurfPolygon = polygonToTurfPolygon(rectToPolygon(object)) + } + + const intersection = turf.intersect(turf.featureCollection([dormerTurfPolygon, tempTurfModule])) //겹치는지 확인 + //겹치면 안됨 + if (intersection) { + alert('도머위에 모듈을 올릴 수 없습니다.') + isIntersection = false + } + }) + } + + if (!isIntersection) return + + tempModule.setCoords() //좌표 재정렬 + + if (turf.booleanWithin(tempTurfModule, turfPolygon)) { + //마우스 클릭시 set으로 해당 위치에 셀을 넣음 + const isOverlap = manualDrawModules.some((module) => turf.booleanOverlap(tempTurfModule, polygonToTurfPolygon(module))) //겹치는지 확인 + if (!isOverlap) { + //안겹치면 넣는다 + tempModule.setCoords() + tempModule.set({ name: 'module', fill: '#BFFD9F' }) + manualDrawModules.push(tempModule) //모듈배열에 추가 + //해당 모듈에 프로퍼티로 넣는다 + trestlePolygon.set({ + modules: manualDrawModules, + }) + } else { + alert('셀끼리 겹치면 안되죠?') + } + } else { + alert('나갔죠?!!') + } + } + }) + } + } + + //자동 모듈 설치(그리드 방식) + const autoModuleSetup = (placementRef) => { + const isChidori = placementRef.isChidori.current + const setupLocation = placementRef.setupLocation.current + const isMaxSetup = placementRef.isMaxSetup.current + + initEvent() + const moduleSetupSurfaces = moduleSetupSurface //선택 설치면 + + const notSelectedTrestlePolygons = canvas + ?.getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && !moduleSetupSurfaces.includes(obj)) //설치면이 아닌것 + + const batchObjects = canvas + ?.getObjects() + .filter( + (obj) => + obj.name === BATCH_TYPE.OPENING || + obj.name === BATCH_TYPE.TRIANGLE_DORMER || + obj.name === BATCH_TYPE.PENTAGON_DORMER || + obj.name === BATCH_TYPE.SHADOW, + ) //도머s 객체 + + if (moduleSetupSurfaces.length === 0) { + alert('선택된 모듈 설치면이 없습니다.') + return + } + + if (moduleIsSetup.length > 0) { + alert('기존 모듈은 제거됩니다.') + moduleIsSetup.forEach((module) => { + canvas?.remove(module) + }) + } + + notSelectedTrestlePolygons.forEach((obj) => { + if (obj.modules) { + obj.modules.forEach((module) => { + canvas?.remove(module) + }) + obj.modules = [] + } + }) + + const moduleSetupArray = [] + moduleSetupSurfaces.forEach((moduleSetupSurface, index) => { + moduleSetupSurface.fire('mousedown') + + const surfaceMaxLines = findSetupSurfaceMaxLines(moduleSetupSurface) + + let maxLengthLine = moduleSetupSurface.lines.reduce((acc, cur) => { + return acc.length > cur.length ? acc : cur + }) + + const turfModuleSetupSurface = polygonToTurfPolygon(moduleSetupSurface) //폴리곤을 turf 객체로 변환 + + const containsBatchObjects = batchObjects.filter((batchObject) => { + let convertBatchObject + + if (batchObject.type === 'group') { + //도머는 그룹형태임 + convertBatchObject = batchObjectGroupToTurfPolygon(batchObject) + } else { + //개구, 그림자 + batchObject.set({ + points: rectToPolygon(batchObject), + }) + canvas?.renderAll() // set된걸 바로 적용하기 위해 + convertBatchObject = polygonToTurfPolygon(batchObject) //rect를 폴리곤으로 변환 -> turf 폴리곤으로 변환 + } + + // 폴리곤 안에 도머 폴리곤이 포함되어있는지 확인해서 반환하는 로직 + return turf.booleanContains(turfModuleSetupSurface, convertBatchObject) || turf.booleanWithin(convertBatchObject, turfModuleSetupSurface) + }) + + let difference = turfModuleSetupSurface //기본 객체(면형상) + + if (containsBatchObjects.length > 0) { + //turf로 도머를 제외시키는 로직 + for (let i = 0; i < containsBatchObjects.length; i++) { + let convertBatchObject + if (containsBatchObjects[i].type === 'group') { + convertBatchObject = batchObjectGroupToTurfPolygon(containsBatchObjects[i]) + } else { + convertBatchObject = polygonToTurfPolygon(containsBatchObjects[i]) + } + } + } + + let width = maxLengthLine.flowDirection === 'east' || maxLengthLine.flowDirection === 'west' ? 172.2 : 113.4 + let height = maxLengthLine.flowDirection === 'east' || maxLengthLine.flowDirection === 'west' ? 113.4 : 172.2 + + //배치면때는 방향쪽으로 패널이 넓게 누워져야함 + if (moduleSetupSurface.flowDirection !== undefined) { + width = moduleSetupSurface.flowDirection === 'south' || moduleSetupSurface.flowDirection === 'north' ? 172.2 : 113.4 + height = moduleSetupSurface.flowDirection === 'south' || moduleSetupSurface.flowDirection === 'north' ? 113.4 : 172.2 + } + + let square + let startPoint, endPoint + + if (setupLocation === 'eaves') { + if (moduleSetupSurface.flowDirection === 'south') { + startPoint = flowModuleLine.find((obj) => obj.target === 'bottom') + endPoint = flowModuleLine.find((obj) => obj.target === 'top') + const totalHeight = endPoint.y1 - startPoint.y1 + const diffHeight = Math.abs(totalHeight / height) + let leftMargin = 0 + let bottomMargin = 0 + + for (let i = 0; i < diffHeight; i++) { + leftMargin = i === 0 ? 1 : 0 + bottomMargin = i === 0 ? 0 : 1 + + square = [ + [startPoint.x1 + leftMargin, startPoint.y1 - height - bottomMargin], + [startPoint.x1 + leftMargin, startPoint.y1 - bottomMargin], + [startPoint.x1 + leftMargin + width, startPoint.y1 - bottomMargin], + [startPoint.x1 + leftMargin + width, startPoint.y1 - height - bottomMargin], + [startPoint.x1 + leftMargin, startPoint.y1 - height - bottomMargin], + ] + + const squarePolygon = turf.polygon([square]) + + //설치면 안에 있는지 확인 + const disjointFromTrestle = + turf.booleanContains(turfModuleSetupSurface, squarePolygon) || turf.booleanWithin(squarePolygon, turfModuleSetupSurface) + + if (disjointFromTrestle) { + let turfCoordnates = squarePolygon.geometry.coordinates[0].slice(0, -1) + const points = turfCoordnates.map((coord) => ({ x: coord[0], y: coord[1] })) + + if (containsBatchObjects.length > 0) { + let convertBatchObject + //도머가 있으면 적용되는 로직 + const isDisjoint = containsBatchObjects.every((batchObject) => { + if (batchObject.type === 'group') { + convertBatchObject = batchObjectGroupToTurfPolygon(batchObject) + } else { + convertBatchObject = polygonToTurfPolygon(batchObject) + } + return turf.booleanDisjoint(squarePolygon, convertBatchObject) //도머가 여러개일수있으므로 겹치는게 있다면... + }) + if (isDisjoint) { + const tempModule = new QPolygon(points, { + fill: '#BFFD9F', + stroke: 'black', + strokeWidth: 0.1, + selectable: true, // 선택 가능하게 설정 + lockMovementX: false, // X 축 이동 잠금 + lockMovementY: false, // Y 축 이동 잠금 + lockRotation: false, // 회전 잠금 + lockScalingX: false, // X 축 크기 조정 잠금 + lockScalingY: false, // Y 축 크기 조정 잠금 + opacity: 0.8, + parentId: moduleSetupSurface.parentId, + name: 'module', + }) + tempModule.setViewLengthText(false) + canvas?.add(tempModule) + moduleSetupArray.push(tempModule) + } + } else { + //도머가 없을땐 그냥 그림 + const tempModule = new QPolygon(points, { + fill: '#BFFD9F', + stroke: 'black', + selectable: true, // 선택 가능하게 설정 + lockMovementX: true, // X 축 이동 잠금 + lockMovementY: true, // Y 축 이동 잠금 + lockRotation: true, // 회전 잠금 + lockScalingX: true, // X 축 크기 조정 잠금 + lockScalingY: true, // Y 축 크기 조정 잠금 + opacity: 0.8, + parentId: moduleSetupSurface.parentId, + name: 'module', + }) + canvas?.add(tempModule) + moduleSetupArray.push(tempModule) + } + startPoint = { x1: points[0].x, y1: points[0].y, x2: points[3].x, y2: points[3].y } + } + } + } + } else if (setupLocation === 'ridge') { + } else { + } + + moduleSetupSurface.set({ modules: moduleSetupArray }) + }) + + setModuleIsSetup(moduleSetupArray) + + console.log(calculateForApi(moduleSetupArray)) + } + + const calculateForApi = (moduleSetupArray) => { + const centerPoints = [] + moduleSetupArray.forEach((module, index) => { + module.tempIndex = index + const { x, y } = module.getCenterPoint() + const { width, height } = module + centerPoints.push({ x, y, width, height, index }) + const circle = new fabric.Circle({ + radius: 5, + fill: 'red', + name: 'redCircle', + left: x - 5, + top: y - 5, + index: index, + selectable: false, + }) + canvas.add(circle) + }) + + //완전 노출 하면 + let exposedBottom = 0 + // 반 노출 하면 + let exposedHalfBottom = 0 + // 완전 노출 상면 + let exposedTop = 0 + //반 노출 상면 + let exposedHalfTop = 0 + // 완전 접면 + let touchDimension = 0 + //반접면 + let halfTouchDimension = 0 + // 노출하면 체크 + centerPoints.forEach((centerPoint, index) => { + const { x, y, width, height } = centerPoint + // centerPoints중에 현재 centerPoint와 x값이 같고, y값이 y-height값과 같은 centerPoint가 있는지 확인 + const bottomCell = centerPoints.filter((centerPoint) => centerPoint.x === x && Math.abs(centerPoint.y - (y + height)) < 2) + if (bottomCell.length === 1) { + touchDimension++ + return + } + + const bottomLeftPoint = { x: x - width / 2, y: y + height } + const bottomRightPoint = { x: x + width / 2, y: y + height } + + // 바로 아래에 셀이 없는 경우 물떼세 배치가 왼쪽 되어있는 셀을 찾는다. + const leftBottomCnt = centerPoints.filter( + (centerPoint) => Math.abs(centerPoint.x - bottomLeftPoint.x) < 2 && Math.abs(centerPoint.y - bottomLeftPoint.y) < 2, + ).length + const rightBottomCnt = centerPoints.filter( + (centerPoint) => Math.abs(centerPoint.x - bottomRightPoint.x) < 2 && Math.abs(centerPoint.y - bottomRightPoint.y) < 2, + ).length + if (leftBottomCnt + rightBottomCnt === 2) { + touchDimension++ + return + } + if (leftBottomCnt + rightBottomCnt === 1) { + halfTouchDimension++ + exposedHalfBottom++ + return + } + if (leftBottomCnt + rightBottomCnt === 0) { + exposedBottom++ + return + } + }) + // 노출상면 체크 + + centerPoints.forEach((centerPoint, index) => { + const { x, y, width, height } = centerPoint + const topCell = centerPoints.filter((centerPoint) => centerPoint.x === x && Math.abs(centerPoint.y - (y - height)) < 2) + if (topCell.length === 1) { + return + } + + const topLeftPoint = { x: x - width / 2, y: y - height } + const topRightPoint = { x: x + width / 2, y: y - height } + + const leftTopCnt = centerPoints.filter( + (centerPoint) => Math.abs(centerPoint.x - topLeftPoint.x) < 2 && Math.abs(centerPoint.y - topRightPoint.y) < 2, + ).length + const rightTopCnt = centerPoints.filter( + (centerPoint) => Math.abs(centerPoint.x - topRightPoint.x) < 2 && Math.abs(centerPoint.y - topRightPoint.y) < 2, + ).length + + if (leftTopCnt + rightTopCnt === 1) { + exposedHalfTop++ + return + } + if (leftTopCnt + rightTopCnt === 0) { + exposedTop++ + return + } + }) + return { + exposedBottom, + exposedHalfBottom, + exposedTop, + exposedHalfTop, + touchDimension, + halfTouchDimension, + } + } + + const coordToTurfPolygon = (points) => { + const coordinates = points.map((point) => [point.x, point.y]) + coordinates.push(coordinates[0]) + return turf.polygon([coordinates]) + } + + const polygonToTurfPolygon = (object) => { + let coordinates + coordinates = object.points.map((point) => [point.x, point.y]) + coordinates.push(coordinates[0]) + return turf.polygon( + [coordinates], + {}, + { + parentId: object.parentId, + }, + ) + } + + const batchObjectGroupToTurfPolygon = (group) => { + const polygons = group.getObjects().filter((obj) => obj.type === 'QPolygon') + let allPoints = [] + + polygons.forEach((obj) => allPoints.push(...obj.get('points'))) + + const points = turf.featureCollection(allPoints.map((point) => turf.point([point.x, point.y]))) + const hull = turf.concave(points, { tolerance: 0.1 }) + + return hull + } + + const bottomTopFlowLine = (surface) => { + const flowArray = [] + const bottomFlow = surface.lines.reduce( + (acc, line, index) => { + if (line.y1 > acc.y1 || (line.y1 === acc.y1 && line.x1 > acc.x1)) { + return { x1: line.x1, y1: line.y1, index: index } + } + return acc + }, + { x1: 0, y1: 0, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 + ) + flowArray.push(bottomFlow) + + const topFlow = surface.lines.reduce( + (acc, line, index) => { + if (line.y1 < acc.y1 || (line.y1 === acc.y1 && line.x1 < acc.x1)) { + return { x1: line.x1, y1: line.y1, index: index } + } + return acc + }, + { x1: Infinity, y1: Infinity, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 + ) + flowArray.push(topFlow) + + let idx = 0 + let rtnObjArray = [] + flowArray.forEach((center) => { + const linesArray = new Array() + + surface.lines.filter((line) => { + if ((center.x1 === line.x1 && center.y1 === line.y1) || (center.x1 === line.x2 && center.y1 === line.y2)) { + linesArray.push(line) + } + }) + + let coords = [] + if (center.index === 0) { + coords = [ + { x: linesArray[0].x2, y: linesArray[0].y2 }, + { x: center.x1, y: center.y1 }, + { x: linesArray[1].x1, y: linesArray[1].y1 }, + ] + } else { + coords = [ + { x: linesArray[0].x1, y: linesArray[0].y1 }, + { x: center.x1, y: center.y1 }, + { x: linesArray[1].x2, y: linesArray[1].y2 }, + ] + } + + const adjust1 = coords[0].x - coords[1].x + const height1 = coords[1].y - coords[0].y + const angle1 = Math.abs(Math.round(Math.atan(height1 / adjust1) * (180 / Math.PI) * 1000) / 1000) + + const adjust2 = coords[2].x - coords[1].x + const height2 = coords[2].y - coords[1].y + const angle2 = Math.abs(Math.round(Math.atan(height2 / adjust2) * (180 / Math.PI) * 1000) / 1000) + const angle3 = 180 - (angle1 + angle2) + + const charlie = 173.3 + 3 // 평행선길이 약간 여유를 줌 + const alpha = (charlie * Math.sin((angle1 * Math.PI) / 180)) / Math.sin((angle3 * Math.PI) / 180) + const beta = Math.sqrt(alpha ** 2 + charlie ** 2 - 2 * alpha * charlie * Math.cos((angle2 * Math.PI) / 180)) + const h = beta * Math.sin((angle1 * Math.PI) / 180) // 높이 + const sign = Math.sign(coords[0].y - coords[1].y) // 진행방향 + const top = coords[1].y + sign * h // 변경되는 높이 좌표 값 + // const line3 = new QLine([coords[1].x, coords[1].y, coords[1].x, top], { + // stroke: 'blue', + // strokeWidth: 1, + // selectable: true, + // }) + // // canvas?.add(line3) + + const pointX1 = coords[0].x + ((coords[0].y - top) / (coords[0].y - coords[1].y)) * (coords[1].x - coords[0].x) + const pointY1 = top + const pointX2 = coords[2].x + ((coords[2].y - top) / (coords[2].y - coords[1].y)) * (coords[1].x - coords[2].x) + const pointY2 = top + + const finalLine = new QLine([pointX1, pointY1, pointX2, pointY2], { + stroke: 'red', + strokeWidth: 1, + selectable: true, + }) + canvas?.add(finalLine) + canvas?.renderAll() + + const rtnObj = { target: idx === 0 ? 'bottom' : 'top', x1: pointX1, y1: pointY1, x2: pointX2, y2: pointY2 } + rtnObjArray.push(rtnObj) + ++idx + }) + + return rtnObjArray + } + + const leftRightFlowLine = (surface) => { + const flowArray = [] + const leftFlow = surface.lines.reduce( + (acc, line, index) => { + if (line.x1 < acc.x1 || (line.x1 === acc.x1 && line.y1 < acc.y1)) { + return { x1: line.x1, y1: line.y1, index: index } + } + return acc + }, + { x1: Infinity, y1: Infinity, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 + ) + flowArray.push(leftFlow) + + const rightFlow = surface.lines.reduce( + (acc, line, index) => { + if (line.x1 > acc.x1 || (line.x1 === acc.x1 && line.y1 > acc.y1)) { + return { x1: line.x1, y1: line.y1, index: index } + } + return acc + }, + { x1: 0, y1: 0, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 + ) + flowArray.push(rightFlow) + + let idx = 0 + let rtnObjArray = [] + flowArray.forEach((center) => { + const linesArray = surface.lines.filter((line) => { + if ((center.x1 === line.x1 && center.y1 === line.y1) || (center.x1 === line.x2 && center.y1 === line.y2)) { + return line + } + }) + + let coords = [] + if (center.index === 0) { + coords = [ + { x: linesArray[1].x1, y: linesArray[1].y1 }, + { x: center.x1, y: center.y1 }, + { x: linesArray[0].x2, y: linesArray[0].y2 }, + ] + } else { + coords = [ + { x: linesArray[0].x1, y: linesArray[0].y1 }, + { x: center.x1, y: center.y1 }, + { x: linesArray[1].x2, y: linesArray[1].y2 }, + ] + } + + const adjust1 = coords[0].x - coords[1].x + const height1 = coords[1].y - coords[0].y + const angle1 = Math.abs(Math.round(Math.atan(adjust1 / height1) * (180 / Math.PI) * 1000) / 1000) + + const adjust2 = coords[2].x - coords[1].x + const height2 = coords[2].y - coords[1].y + const angle2 = Math.abs(Math.round(Math.atan(adjust2 / height2) * (180 / Math.PI) * 1000) / 1000) + const angle3 = 180 - (angle1 + angle2) + + const charlie = 173.3 + 3 // 평행선길이 약간 여유를줌 + const alpha = (charlie * Math.sin((angle1 * Math.PI) / 180)) / Math.sin((angle3 * Math.PI) / 180) + const beta = Math.sqrt(alpha ** 2 + charlie ** 2 - 2 * alpha * charlie * Math.cos((angle2 * Math.PI) / 180)) + + const h = beta * Math.sin((angle1 * Math.PI) / 180) // 높이 + const sign = Math.sign(coords[0].x - coords[1].x) // 진행방향 + const top = coords[1].x + sign * h // 변경되는 높이 좌표 값 + + // const line3 = new QLine([coords[1].x, coords[1].y, top, coords[1].y], { + // stroke: 'blue', + // strokeWidth: 1, + // selectable: true, + // }) + // canvas?.add(line3) + + const pointX1 = top + const pointY1 = coords[0].y + ((coords[0].x - top) / (coords[0].x - coords[1].x)) * (coords[1].y - coords[0].y) + const pointX2 = top + const pointY2 = coords[2].y + ((coords[2].x - top) / (coords[2].x - coords[1].x)) * (coords[1].y - coords[2].y) + + const finalLine = new QLine([pointX1, pointY1, pointX2, pointY2], { + stroke: 'red', + strokeWidth: 1, + selectable: true, + }) + canvas?.add(finalLine) + canvas?.renderAll() + + const rtnObj = { target: idx === 0 ? 'left' : 'right', x1: pointX1, y1: pointY1, x2: pointX2, y2: pointY2 } + rtnObjArray.push(rtnObj) + ++idx + }) + } + + const findSetupSurfaceMaxLines = (surface) => { + const leftFlow = surface.lines.reduce( + (acc, line, index) => { + if (line.x1 < acc.x1 || (line.x1 === acc.x1 && line.y1 < acc.y1)) { + return { x1: line.x1, y1: line.y1 } + } + return acc + }, + { x1: Infinity, y1: Infinity, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 + ) + + const rightFlow = surface.lines.reduce( + (acc, line, index) => { + if (line.x1 > acc.x1 || (line.x1 === acc.x1 && line.y1 > acc.y1)) { + return { x1: line.x1, y1: line.y1 } + } + return acc + }, + { x1: 0, y1: 0, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 + ) + + const topFlow = surface.lines.reduce( + (acc, line, index) => { + if (line.y1 < acc.y1 || (line.y1 === acc.y1 && line.x1 < acc.x1)) { + return { x1: line.x1, y1: line.y1 } + } + return acc + }, + { x1: Infinity, y1: Infinity, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 + ) + + const bottomFlow = surface.lines.reduce( + (acc, line, index) => { + if (line.y1 > acc.y1 || (line.y1 === acc.y1 && line.x1 > acc.x1)) { + return { x1: line.x1, y1: line.y1 } + } + return acc + }, + { x1: 0, y1: 0, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 + ) + + const obj = { + left: leftFlow, + right: rightFlow, + top: topFlow, + bottom: bottomFlow, + } + + return obj + } + + return { + makeModuleInstArea, + manualModuleSetup, + autoModuleSetup, + } +} diff --git a/src/hooks/module/useOrientation.js b/src/hooks/module/useOrientation.js new file mode 100644 index 00000000..b64fc171 --- /dev/null +++ b/src/hooks/module/useOrientation.js @@ -0,0 +1,36 @@ +import { useRecoilState, useRecoilValue } from 'recoil' +import { canvasState } from '@/store/canvasAtom' +import { usePolygon } from '@/hooks/usePolygon' +import { POLYGON_TYPE } from '@/common/common' +import { compasDegAtom } from '@/store/orientationAtom' +import { useEffect } from 'react' + +// 모듈,회로 구성 탭 기본설정 > 방위설정 탭 +export function useOrientation() { + const canvas = useRecoilValue(canvasState) + const [compasDeg, setCompasDeg] = useRecoilState(compasDegAtom) + + const { drawDirectionArrow } = usePolygon() + + useEffect(() => { + const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) + roofs.forEach((roof) => { + roof.set({ + moduleCompass: null, + }) + drawDirectionArrow(roof) + }) + }, []) + + const nextStep = () => { + const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) + roofs.forEach((roof) => { + roof.set({ + moduleCompass: compasDeg, + }) + drawDirectionArrow(roof) + }) + } + + return { nextStep, compasDeg, setCompasDeg } +} diff --git a/src/hooks/object/useObjectBatch.js b/src/hooks/object/useObjectBatch.js index 1820599b..dd67b017 100644 --- a/src/hooks/object/useObjectBatch.js +++ b/src/hooks/object/useObjectBatch.js @@ -17,6 +17,7 @@ export function useObjectBatch({ isHidden, setIsHidden }) { const { getMessage } = useMessage() const canvas = useRecoilValue(canvasState) const { addCanvasMouseEventListener, initEvent, addDocumentEventListener } = useEvent() + // const { addCanvasMouseEventListener, initEvent, addDocumentEventListener } = useContext(EventContext) const { swalFire } = useSwal() const { drawDirectionArrow } = usePolygon() const lengthTextFont = useRecoilValue(fontSelector('lengthText')) diff --git a/src/hooks/option/useCanvasSetting.js b/src/hooks/option/useCanvasSetting.js index 549cc9a4..c3155512 100644 --- a/src/hooks/option/useCanvasSetting.js +++ b/src/hooks/option/useCanvasSetting.js @@ -1,16 +1,22 @@ -import { useEffect, useState } from 'react' -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' -import { adsorptionPointModeState, adsorptionRangeState, canvasState } from '@/store/canvasAtom' +import { useCallback, useEffect, useState } from 'react' +import { useRecoilState, useRecoilValue } from 'recoil' +import { adsorptionPointModeState, adsorptionRangeState, canvasState, planSizeSettingState } from '@/store/canvasAtom' import { globalLocaleStore } from '@/store/localeAtom' import { useMessage } from '@/hooks/useMessage' import { useAxios } from '@/hooks/useAxios' import { useSwal } from '@/hooks/useSwal' -import { corridorDimensionSelector, settingModalFirstOptionsState, settingModalSecondOptionsState } from '@/store/settingAtom' -import { setSurfaceShapePattern } from '@/util/canvas-util' +import { correntObjectNoState, corridorDimensionSelector, settingModalFirstOptionsState, settingModalSecondOptionsState } from '@/store/settingAtom' import { POLYGON_TYPE } from '@/common/common' +import { globalFontAtom } from '@/store/fontAtom' +import { dimensionLineSettingsState } from '@/store/commonUtilsAtom' + +let objectNo export function useCanvasSetting() { const canvas = useRecoilValue(canvasState) + // canvas가 null이 아닐 때에만 getObjects 호출 + const canvasObjects = canvas ? canvas.getObjects() : [] + const [correntObjectNo, setCorrentObjectNo] = useRecoilState(correntObjectNoState) const [settingModalFirstOptions, setSettingModalFirstOptions] = useRecoilState(settingModalFirstOptionsState) const [settingModalSecondOptions, setSettingModalSecondOptions] = useRecoilState(settingModalSecondOptionsState) @@ -25,9 +31,16 @@ export function useCanvasSetting() { const { swalFire } = useSwal() const [adsorptionPointMode, setAdsorptionPointMode] = useRecoilState(adsorptionPointModeState) - const setAdsorptionRange = useSetRecoilState(adsorptionRangeState) + const [adsorptionRange, setAdsorptionRange] = useRecoilState(adsorptionRangeState) + const [planSizeSettingMode, setPlanSizeSettingMode] = useRecoilState(planSizeSettingState) + //const setAdsorptionRange = useSetRecoilState(adsorptionRangeState) - const [objectNo, setObjectNo] = useState('test123240912001') // 이후 삭제 필요 + const [selectedFont, setSelectedFont] = useState() + const [selectedFontWeight, setSelectedFontWeight] = useState() + const [selectedFontSize, setSelectedFontSize] = useState() + const [selectedFontColor, setSelectedFontColor] = useState() + const [globalFont, setGlobalFont] = useRecoilState(globalFontAtom) + const [dimensionLineSettings, setDimensionLineSettings] = useRecoilState(dimensionLineSettingsState) useEffect(() => { if (!canvas) { @@ -56,199 +69,352 @@ export function useCanvasSetting() { }) break } - canvas.renderAll() + canvas?.renderAll() }, [corridorDimension]) useEffect(() => { - console.log('useCanvasSetting useEffect 실행1') - fetchSettings() - }, [objectNo]) + console.log('useCanvasSetting useEffect 실행1', correntObjectNo) + }, []) + //흡착점 ON/OFF 변경 시 useEffect(() => { - console.log('useCanvasSetting useEffect 실행2') - //fetchSettings() - //onClickOption() - //fetchSettings() + console.log('useCanvasSetting useEffect 실행2', adsorptionPointMode.fontFlag, correntObjectNo) + + if (adsorptionPointMode.fontFlag) { + onClickOption2() + frontSettings() + fetchSettings() + } }, [adsorptionPointMode]) + // 1 과 2 변경 시 useEffect(() => { - console.log('useCanvasSetting useEffect 실행3') - //fetchSettings() - //onClickOption() - //fetchSettings() + console.log('useCanvasSetting useEffect 실행3', settingModalFirstOptions.fontFlag, settingModalSecondOptions.fontFlag, correntObjectNo) + if (settingModalFirstOptions.fontFlag || settingModalSecondOptions.fontFlag) { + onClickOption2() + frontSettings() + fetchSettings() + } }, [settingModalFirstOptions, settingModalSecondOptions]) + // 글꼴 변경 시 + useEffect(() => { + console.log('useCanvasSetting useEffect 실행4', globalFont.fontFlag, correntObjectNo) + if (globalFont.fontFlag) { + onClickOption2() + frontSettings() + fetchSettings() + } + }, [globalFont]) + + // 도명크기 변경 시 + useEffect(() => { + console.log('useCanvasSetting useEffect 실행5', planSizeSettingMode.flag, correntObjectNo) + + if (planSizeSettingMode.flag) { + onClickOption2() + frontSettings() + fetchSettings() + } + }, [planSizeSettingMode]) + + const getFonts = (itemValue) => { + if (!itemValue) return { id: 1, name: 'MS PGothic', value: 'MS PGothic' } + const data = [ + { id: 1, name: 'MS PGothic', value: 'MS PGothic' }, + { id: 2, name: '@Yu Gothic', value: '@Yu Gothic' }, + { id: 3, name: 'Yu Gothic', value: 'Yu Gothic' }, + { id: 4, name: '@Yu Gothic UI', value: '@Yu Gothic UI' }, + { id: 5, name: 'Yu Gothic UI', value: 'Yu Gothic UI' }, + ].filter((font) => font.value === itemValue) + if (data.length !== 0) { + return data[0] + } else { + return { id: 1, name: 'MS PGothic', value: 'MS PGothic' } + } + } + + const getFontSizes = (itemValue) => { + if (!itemValue) return { id: 16, name: 16, value: 16 } + const data = [ + ...Array.from({ length: 4 }).map((_, index) => { + return { id: index + 8, name: index + 8, value: index + 8 } + }), + ...Array.from({ length: 9 }).map((_, index) => { + return { id: (index + 6) * 2, name: (index + 6) * 2, value: (index + 6) * 2 } + }), + { id: 36, name: 36, value: 36 }, + { id: 48, name: 48, value: 48 }, + { id: 72, name: 72, value: 72 }, + ].filter((fontSize) => fontSize.value === itemValue) + if (data.length !== 0) { + return data[0] + } else { + return { id: 16, name: 16, value: 16 } + } + } + + const getFontStyles = (itemValue) => { + if (!itemValue) return { id: 'normal', name: getMessage('font.style.normal'), value: 'normal' } + const data = [ + { id: 'normal', name: getMessage('font.style.normal'), value: 'normal' }, + { id: 'italic', name: getMessage('font.style.italic'), value: 'italic' }, + { id: 'bold', name: getMessage('font.style.bold'), value: 'bold' }, + { id: 'boldAndItalic', name: getMessage('font.style.bold.italic'), value: 'boldAndItalic' }, + ].filter((fontStyle) => fontStyle.value === itemValue) + if (data.length !== 0) { + return data[0] + } else { + return { id: 'normal', name: getMessage('font.style.normal'), value: 'normal' } + } + } + + const getFontColors = (itemValue) => { + if (!itemValue) return { id: 'black', name: getMessage('color.black'), value: 'black' } + const data = [ + { id: 'black', name: getMessage('color.black'), value: 'black' }, + { id: 'red', name: getMessage('color.red'), value: 'red' }, + { id: 'blue', name: getMessage('color.blue'), value: 'blue' }, + { id: 'gray', name: getMessage('color.gray'), value: 'gray' }, + { id: 'yellow', name: getMessage('color.yellow'), value: 'yellow' }, + { id: 'green', name: getMessage('color.green'), value: 'green' }, + { id: 'pink', name: getMessage('color.pink'), value: 'pink' }, + { id: 'gold', name: getMessage('color.gold'), value: 'gold' }, + { id: 'darkblue', name: getMessage('color.darkblue'), value: 'darkblue' }, + ].filter((fontColor) => fontColor.value === itemValue) + if (data.length !== 0) { + return data[0] + } else { + return { id: 'black', name: getMessage('color.black'), value: 'black' } + } + } + const fetchSettings = async () => { try { - const res = await get({ url: `/api/canvas-management/canvas-settings/by-object/${objectNo}` }) + const res = await get({ url: `/api/canvas-management/canvas-settings/by-object/${correntObjectNo}` }) console.log('res', res) - const optionData1 = settingModalFirstOptions.option1.map((item) => ({ ...item, selected: res[item.column] })) - const optionData2 = settingModalFirstOptions.option2.map((item) => ({ ...item, selected: res[item.column] })) - const optionData3 = settingModalSecondOptions.option3.map((item) => ({ ...item })) - const optionData4 = settingModalSecondOptions.option4.map((item) => ({ ...item, selected: res[item.column] })) - const optionData5 = settingModalFirstOptions.dimensionDisplay.map((item) => ({ ...item })) - const patternData = { - adsorpPoint: res.adsorpPoint, + if (res) { + const optionData1 = settingModalFirstOptions.option1.map((item) => ({ ...item, selected: res[item.column] })) + const optionData2 = settingModalFirstOptions.option2.map((item) => ({ ...item, selected: res[item.column] })) + const optionData3 = settingModalSecondOptions.option3.map((item) => ({ ...item })) + const optionData4 = settingModalSecondOptions.option4.map((item) => ({ ...item, selected: res[item.column] })) + const optionData5 = settingModalFirstOptions.dimensionDisplay.map((item) => ({ ...item })) + + //흡착점 ON/OFF + setAdsorptionPointMode({ ...adsorptionPointMode, adsorptionPoint: res.adsorpPoint, fontFlag: false }) + + //치수선 설정 + setDimensionLineSettings({ ...dimensionLineSettings, pixel: res.originPixel, color: res.originColor }) + + //도면크기 설정 + setPlanSizeSettingMode({ + ...planSizeSettingMode, + originHorizon: res.originHorizon, + originVertical: res.originVertical, + flag: false, + }) + + // 데이터 설정 + setSettingModalFirstOptions({ + ...settingModalFirstOptions, + option1: optionData1, + option2: optionData2, + dimensionDisplay: optionData5, + fontFlag: false, + }) + setSettingModalSecondOptions({ + ...settingModalSecondOptions, + option3: optionData3, + option4: optionData4, + fontFlag: false, + }) + + const fontPatternData = { + commonText: { + //문자 글꼴 조회 데이터 + fontFamily: getFonts(res.wordFont), + fontWeight: getFontStyles(res.wordFontStyle), + fontSize: getFontSizes(res.wordFontSize), + fontColor: getFontColors(res.wordFontColor), + }, + flowText: { + //흐름방향 글꼴 조회 데이터 + fontFamily: getFonts(res.flowFont), + fontWeight: getFontStyles(res.flowFontStyle), + fontSize: getFontSizes(res.flowFontSize), + fontColor: getFontColors(res.flowFontColor), + }, + dimensionLineText: { + //치수 글꼴 조회 데이터 + fontFamily: getFonts(res.dimensioFont), + fontWeight: getFontStyles(res.dimensioFontStyle), + fontSize: getFontSizes(res.dimensioFontSize), + fontColor: getFontColors(res.dimensioFontColor), + }, + circuitNumberText: { + //회로번호 글꼴 조회 데이터 + fontFamily: getFonts(res.circuitNumFont), + fontWeight: getFontStyles(res.circuitNumFontStyle), + fontSize: getFontSizes(res.circuitNumFontSize), + fontColor: getFontColors(res.circuitNumFontColor), + }, + lengthText: { + //치수선 글꼴 조회 데이터 + fontFamily: getFonts(res.lengthFont), + fontWeight: getFontStyles(res.lengthFontStyle), + fontSize: getFontSizes(res.lengthFontSize), + fontColor: getFontColors(res.lengthFontColor), + }, + //글꼴 설정 Flag + fontFlag: false, + } + console.log('fontPatternData', fontPatternData) + + //조회된 글꼴 데이터 set + setGlobalFont(fontPatternData) + } else { + //조회된 글꼴 데이터가 없는 경우 + + //흡착점 ON/OFF + setAdsorptionPointMode({ ...adsorptionPointMode, adsorptionPoint: false, fontFlag: false }) + + //치수선 설정 + setDimensionLineSettings({ ...dimensionLineSettings }) + + //도면크기 설정 + setPlanSizeSettingMode({ + ...planSizeSettingMode, + flag: false, + }) + + // 데이터 설정 + setSettingModalFirstOptions({ + ...settingModalFirstOptions, + fontFlag: false, + }) + setSettingModalSecondOptions({ + ...settingModalSecondOptions, + fontFlag: false, + }) + + setGlobalFont({ ...globalFont, fontFlag: false }) } - - // 데이터 설정 - setSettingModalFirstOptions({ - option1: optionData1, - option2: optionData2, - dimensionDisplay: optionData5, - }) - setSettingModalSecondOptions({ - option3: optionData3, - option4: optionData4, - }) - - setAdsorptionPointMode(patternData.adsorpPoint) - - console.log('adsorptionPointMode', adsorptionPointMode) + frontSettings() } catch (error) { console.error('Data fetching error:', error) } } // 옵션 클릭 후 저장 - const onClickOption = async (item) => { - //치수 표시(단 건 선택) - if (item.column === 'corridorDimension' || item.column === 'realDimension' || item.column === 'noneDimension') { - console.log('치수 표시 ', item) - const options = settingModalFirstOptions?.dimensionDisplay.map((option) => { - option.selected = option.id === item.id - return option - }) + const onClickOption2 = useCallback(async () => { + // 서버에 전송할 데이터 + const dataToSend = { + firstOption1: option1.map((item) => ({ + column: item.column, + selected: item.selected, + })), + firstOption2: option2.map((item) => ({ + column: item.column, + selected: item.selected, + })), + firstOption3: dimensionDisplay.map((item) => ({ + column: item.column, + selected: item.selected, + })), + secondOption2: option4.map((item) => ({ + column: item.column, + selected: item.selected, + })), + } + // console.log('globalFont', globalFont) + const patternData = { + //견적서 번호 + objectNo: correntObjectNo, + //디스플레이 설정(다중) + allocDisplay: dataToSend.firstOption1[0].selected, + outlineDisplay: dataToSend.firstOption1[1].selected, + gridDisplay: dataToSend.firstOption1[2].selected, + lineDisplay: dataToSend.firstOption1[3].selected, + wordDisplay: dataToSend.firstOption1[4].selected, + circuitNumDisplay: dataToSend.firstOption1[5].selected, + flowDisplay: dataToSend.firstOption1[6].selected, + trestleDisplay: dataToSend.firstOption1[7].selected, + imageDisplay: dataToSend.firstOption1[8].selected, + totalDisplay: dataToSend.firstOption1[9].selected, + //차수 표시(단 건) + corridorDimension: dataToSend.firstOption3[0].selected, + realDimension: dataToSend.firstOption3[1].selected, + noneDimension: dataToSend.firstOption3[2].selected, + //화면 표시(단 건) + onlyBorder: dataToSend.firstOption2[0].selected, + lineHatch: dataToSend.firstOption2[1].selected, + allPainted: dataToSend.firstOption2[2].selected, + //흡착범위 설정(단 건) + adsorpRangeSmall: dataToSend.secondOption2[0].selected, + adsorpRangeSmallSemi: dataToSend.secondOption2[1].selected, + adsorpRangeMedium: dataToSend.secondOption2[2].selected, + adsorpRangeLarge: dataToSend.secondOption2[3].selected, + //흡착점 ON/OFF + adsorpPoint: adsorptionPointMode.adsorptionPoint, + //??: adsorptionRange, 사용여부 확인 필요 - //화면 표시(단 건 선택) - } else if (item.column === 'onlyBorder' || item.column === 'lineHatch' || item.column === 'allPainted') { - console.log('화면 표시 ', item) - const options2 = settingModalFirstOptions?.option2.map((option2) => { - option2.selected = option2.id === item.id - return option2 - }) + //글꼴 설정 + //문자 글꼴 + wordFont: globalFont.commonText.fontFamily?.value ?? 'MS PGothic', + wordFontStyle: globalFont.commonText.fontWeight?.value ?? 'normal', + wordFontSize: globalFont.commonText.fontSize?.value ?? 16, + wordFontColor: globalFont.commonText.fontColor?.value ?? 'black', - const polygons = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) + //흐름방향 글꼴 + flowFont: globalFont.flowText.fontFamily?.value ?? 'MS PGothic', + flowFontStyle: globalFont.flowText.fontWeight?.value ?? 'normal', + flowFontSize: globalFont.flowText.fontSize?.value ?? 16, + flowFontColor: globalFont.flowText.fontColor?.value ?? 'black', - polygons.forEach((polygon) => { - setSurfaceShapePattern(polygon, item.column) - }) + //치수 글꼴 + dimensioFont: globalFont.dimensionLineText.fontFamily?.value ?? 'MS PGothic', + dimensioFontStyle: globalFont.dimensionLineText.fontWeight?.value ?? 'normal', + dimensioFontSize: globalFont.dimensionLineText.fontSize?.value ?? 16, + dimensioFontColor: globalFont.dimensionLineText.fontColor?.value ?? 'black', - //흡착범위 설정(단 건 선택) - } else if ( - item.column === 'adsorpRangeSmall' || - item.column === 'adsorpRangeSmallSemi' || - item.column === 'adsorpRangeMedium' || - item.column === 'adsorpRangeLarge' - ) { - console.log('화면 표시2 ', item, option4) - // option4에서 한 개만 선택 가능하도록 처리 - const updatedOption4 = option4.map((option) => - option.id === item.id - ? { ...option, selected: true } - : { - ...option, - selected: false, - }, - ) + //회로번호 글꼴 + circuitNumFont: globalFont.circuitNumberText.fontFamily?.value ?? 'MS PGothic', + circuitNumFontStyle: globalFont.circuitNumberText.fontWeight?.value ?? 'normal', + circuitNumFontSize: globalFont.circuitNumberText.fontSize?.value ?? 16, + circuitNumFontColor: globalFont.circuitNumberText.fontColor?.value ?? 'black', - setSettingModalSecondOptions({ option3, option4: updatedOption4 }) + //치수선 글꼴 + lengthFont: globalFont.lengthText.fontFamily?.value ?? 'MS PGothic', + lengthFontStyle: globalFont.lengthText.fontWeight?.value ?? 'normal', + lengthFontSize: globalFont.lengthText.fontSize?.value ?? 16, + lengthFontColor: globalFont.lengthText.fontColor?.value ?? 'black', - //흡착점 ON / OFF - } else if (item === 'adsorpPoint') { - console.log('흡착점 ON / OFF ', item) - const options2 = settingModalFirstOptions?.option2.map((option2) => { - option2.selected = option2.id === item.id - return option2 - }) + //치수선 설정 + originPixel: dimensionLineSettings.pixel, + originColor: dimensionLineSettings.color, - const polygons = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) - - polygons.forEach((polygon) => { - setSurfaceShapePattern(polygon, item.column) - }) - - //디스플레이 설정(다 건 선택) - } else { - //console.log('디스플레이 설정1 ', item.column) - console.log('디스플레이 설정 ', item) - item.selected = !item.selected + //치수선 설정 + originHorizon: planSizeSettingMode.originHorizon, + originVertical: planSizeSettingMode.originVertical, } - setSettingModalFirstOptions({ option1, option2, dimensionDisplay }) + console.log('patternData ', patternData) - try { - // 서버에 전송할 데이터 - const dataToSend = { - firstOption1: option1.map((item) => ({ - column: item.column, - selected: item.selected, - })), - firstOption2: option2.map((item) => ({ - column: item.column, - selected: item.selected, - })), - firstOption3: dimensionDisplay.map((item) => ({ - column: item.column, - selected: item.selected, - })), - // secondOption1: secondOptions[0].option1.map((item) => ({ - // name: item.id, - // name: item.name, - // // 필요한 경우 데이터 항목 추가 - // })), - secondOption2: option4.map((item) => ({ - column: item.column, - selected: item.selected, - })), - } - - const patternData = { - objectNo, - //디스플레이 설정(다중) - allocDisplay: dataToSend.firstOption1[0].selected, - outlineDisplay: dataToSend.firstOption1[1].selected, - gridDisplay: dataToSend.firstOption1[2].selected, - lineDisplay: dataToSend.firstOption1[3].selected, - wordDisplay: dataToSend.firstOption1[4].selected, - circuitNumDisplay: dataToSend.firstOption1[5].selected, - flowDisplay: dataToSend.firstOption1[6].selected, - trestleDisplay: dataToSend.firstOption1[7].selected, - imageDisplay: dataToSend.firstOption1[8].selected, - totalDisplay: dataToSend.firstOption1[9].selected, - //차수 표시(단 건) - corridorDimension: dataToSend.firstOption3[0].selected, - realDimension: dataToSend.firstOption3[1].selected, - noneDimension: dataToSend.firstOption3[2].selected, - //화면 표시(단 건) - onlyBorder: dataToSend.firstOption2[0].selected, - lineHatch: dataToSend.firstOption2[1].selected, - allPainted: dataToSend.firstOption2[2].selected, - //흡착범위 설정(단 건) - adsorpRangeSmall: dataToSend.secondOption2[0].selected, - adsorpRangeSmallSemi: dataToSend.secondOption2[1].selected, - adsorpRangeMedium: dataToSend.secondOption2[2].selected, - adsorpRangeLarge: dataToSend.secondOption2[3].selected, - //흡착점 ON/OFF - adsorpPoint: adsorptionPointMode, - } - - console.log('patternData ', patternData) - - // HTTP POST 요청 보내기 - await post({ url: `/api/canvas-management/canvas-settings`, data: patternData }).then((res) => { + // HTTP POST 요청 보내기 + await post({ url: `/api/canvas-management/canvas-settings`, data: patternData }) + .then((res) => { swalFire({ text: getMessage(res.returnMessage) }) // Canvas 디스플레이 설정 시 해당 옵션 적용 frontSettings() }) - } catch (error) { - swalFire({ text: getMessage(res.returnMessage), icon: 'error' }) - } + .catch((error) => { + swalFire({ text: getMessage(res.returnMessage), icon: 'error' }) + }) - setAdsorptionRange(item.range) - } + //setAdsorptionRange(item.range) + }, [settingModalFirstOptions, settingModalSecondOptions, adsorptionPointMode, globalFont, planSizeSettingMode]) // Canvas 디스플레이 설정 시 해당 옵션 적용 const frontSettings = async () => { @@ -260,7 +426,7 @@ export function useCanvasSetting() { // 'lineDisplay' 지붕선 표시 'roof', POLYGON_TYPE.ROOF // 'wordDisplay' 문자 표시 // 'circuitNumDisplay' 회로번호 표시 - // 'flowDisplay' 흐름방향 표시 'arrow' + // 'flowDisplay' 흐름방향 표시 'arrow', 'flowText' // 'trestleDisplay' 가대 표시 // 'imageDisplay' 이미지 표시 // 'totalDisplay' 집계표 표시 @@ -277,13 +443,13 @@ export function useCanvasSetting() { optionName = ['outerLine', POLYGON_TYPE.WALL] break case 'gridDisplay': //그리드 표시 - optionName = ['lindGrid', 'dotGrid'] + optionName = ['lindGrid', 'dotGrid', 'tempGrid'] break case 'lineDisplay': //지붕선 표시 optionName = ['roof', POLYGON_TYPE.ROOF] break case 'wordDisplay': //문자 표시 - optionName = ['6'] + optionName = ['commonText'] break case 'circuitNumDisplay': //회로번호 표시 optionName = ['7'] @@ -304,8 +470,8 @@ export function useCanvasSetting() { // 표시 선택 상태(true/false) optionSelected = option1[i].selected - canvas - .getObjects() + //canvas.getObjects() >> canvasObjects + canvasObjects .filter((obj) => optionName.includes(obj.name)) //.filter((obj) => obj.name === optionName) .forEach((obj) => { @@ -313,6 +479,8 @@ export function useCanvasSetting() { //obj.set({ visible: !obj.visible }) }) + canvas?.renderAll() + // console.log( // 'optionName', // optionName, @@ -322,14 +490,31 @@ export function useCanvasSetting() { } return { + canvas, settingModalFirstOptions, setSettingModalFirstOptions, settingModalSecondOptions, setSettingModalSecondOptions, adsorptionPointMode, setAdsorptionPointMode, + adsorptionRange, + setAdsorptionRange, fetchSettings, - onClickOption, + //onClickOption, frontSettings, + globalFont, + setGlobalFont, + selectedFont, + setSelectedFont, + selectedFontWeight, + setSelectedFontWeight, + selectedFontSize, + setSelectedFontSize, + selectedFontColor, + setSelectedFontColor, + dimensionLineSettings, + setDimensionLineSettings, + planSizeSettingMode, + setPlanSizeSettingMode, } } diff --git a/src/hooks/option/useFirstOption.js b/src/hooks/option/useFirstOption.js deleted file mode 100644 index c296a942..00000000 --- a/src/hooks/option/useFirstOption.js +++ /dev/null @@ -1,81 +0,0 @@ -import { useRecoilState, useRecoilValue } from 'recoil' -import { canvasState } from '@/store/canvasAtom' -import { useEffect } from 'react' -import { settingModalFirstOptionsState } from '@/store/settingAtom' -import { POLYGON_TYPE } from '@/common/common' - -export function useFirstOption() { - const canvas = useRecoilValue(canvasState) - - const [settingModalFirstOptions, setSettingModalFirstOptions] = useRecoilState(settingModalFirstOptionsState) - - useEffect(() => { - const option1 = settingModalFirstOptions.option1 - - // 'allocDisplay' 할당 표시 - // 'outlineDisplay' 외벽선 표시 'outerLine', POLYGON_TYPE.WALL - // 'gridDisplay' 그리드 표시 'lindGrid', 'dotGrid' - // 'lineDisplay' 지붕선 표시 'roof', POLYGON_TYPE.ROOF - // 'wordDisplay' 문자 표시 - // 'circuitNumDisplay' 회로번호 표시 - // 'flowDisplay' 흐름방향 표시 'arrow' - // 'trestleDisplay' 가대 표시 - // 'totalDisplay' 집계표 표시 - - let optionName //옵션명 - let optionSelected //옵션상태 - - for (let i = 0; i < option1.length; i++) { - switch (option1[i].column) { - case 'allocDisplay': //할당 표시 - optionName = ['1'] - break - case 'outlineDisplay': //외벽선 표시 - optionName = ['outerLine', POLYGON_TYPE.WALL] - break - case 'gridDisplay': //그리드 표시 - optionName = ['lineGrid', 'dotGrid', 'adsorptionPoint', 'tempGrid'] - break - case 'lineDisplay': //지붕선 표시 - optionName = ['roof', POLYGON_TYPE.ROOF] - break - case 'wordDisplay': //문자 표시 - optionName = ['6'] - break - case 'circuitNumDisplay': //회로번호 표시 - optionName = ['7'] - break - case 'flowDisplay': //흐름방향 표시 - optionName = ['arrow', 'flowText'] - break - case 'trestleDisplay': //가대 표시 - optionName = ['8'] - break - case 'totalDisplay': //집계표 표시 - optionName = ['9'] - break - } - // 표시 선택 상태(true/false) - optionSelected = option1[i].selected - - canvas - .getObjects() - .filter((obj) => optionName.includes(obj.name)) - //.filter((obj) => obj.name === optionName) - .forEach((obj) => { - obj.set({ visible: optionSelected }) - //obj.set({ visible: !obj.visible }) - }) - - canvas.renderAll() - - // console.log( - // 'optionName', - // optionName, - // canvas.getObjects().filter((obj) => optionName.includes(obj.name)), - // ) - } - }, [settingModalFirstOptions]) - - return { settingModalFirstOptions, setSettingModalFirstOptions } -} diff --git a/src/hooks/roofcover/useAuxiliaryDrawing.js b/src/hooks/roofcover/useAuxiliaryDrawing.js index 6e95e79c..55053b7e 100644 --- a/src/hooks/roofcover/useAuxiliaryDrawing.js +++ b/src/hooks/roofcover/useAuxiliaryDrawing.js @@ -15,20 +15,19 @@ import { outerLineLength2State, outerLineTypeState, } from '@/store/outerLineAtom' -import { calculateIntersection, distanceBetweenPoints, findClosestPoint, isPointOnLine, polygonToTurfPolygon } from '@/util/canvas-util' +import { calculateIntersection, distanceBetweenPoints, findClosestPoint, isPointOnLine } from '@/util/canvas-util' import { fabric } from 'fabric' import { useAdsorptionPoint } from '@/hooks/useAdsorptionPoint' import { useSwal } from '@/hooks/useSwal' -import { booleanPointInPolygon } from '@turf/turf' import { usePopup } from '@/hooks/usePopup' import { calculateAngle, isSamePoint } from '@/util/qpolygon-utils' -import { QPolygon } from '@/components/fabric/QPolygon' import { POLYGON_TYPE } from '@/common/common' // 보조선 작성 export function useAuxiliaryDrawing(id) { const canvas = useRecoilValue(canvasState) const { addCanvasMouseEventListener, addDocumentEventListener, removeMouseLine, initEvent } = useEvent() + // const { addCanvasMouseEventListener, addDocumentEventListener, removeMouseLine, initEvent } = useContext(EventContext) const { getIntersectMousePoint } = useMouse() const { addLine, removeLine } = useLine() const { tempGridMode } = useTempGrid() @@ -94,7 +93,7 @@ export function useAuxiliaryDrawing(id) { addCanvasMouseEventListener('mouse:move', mouseMove) addCanvasMouseEventListener('mouse:down', mouseDown) - addDocumentEventListener('contextmenu', document, cutAuxiliary) + // addDocumentEventListener('contextmenu', document, cutAuxiliary) addDocumentEventListener('keydown', document, keydown[type]) return () => { @@ -122,6 +121,21 @@ export function useAuxiliaryDrawing(id) { setOuterLineDiagonalLength(0) } + const move = (object, x, y) => { + const line = copy(object, x, y) + canvas.remove(object) + canvas.setActiveObject(line) + } + + const copy = (object, x, y) => { + return addLine([object.x1 + x, object.y1 + y, object.x2 + x, object.y2 + y], { + stroke: 'red', + strokeWidth: 1, + selectable: true, + name: 'auxiliaryLine', + }) + } + const keydown = { outerLine: (e) => { if (mousePointerArr.current.length === 0) { @@ -130,7 +144,7 @@ export function useAuxiliaryDrawing(id) { // 포커스가 length1에 있지 않으면 length1에 포커스를 줌 const activeElem = document.activeElement if (activeElem !== length1Ref.current) { - length1Ref.current.focus() + length1Ref?.current?.focus() } const key = e.key @@ -180,7 +194,7 @@ export function useAuxiliaryDrawing(id) { const activeElem = document.activeElement if (activeElem !== length1Ref.current && activeElem !== length2Ref.current) { - length1Ref.current.focus() + length1Ref?.current?.focus() } switch (key) { @@ -455,9 +469,24 @@ export function useAuxiliaryDrawing(id) { name: 'auxiliaryLine', }) - lineHistory.current.push(line) + const historyLines = [...lineHistory.current] + + const hasSameLine = historyLines.some((history) => { + return ( + (isSamePoint(history.startPoint, line.startPoint) && isSamePoint(history.endPoint, line.endPoint)) || + (isSamePoint(history.startPoint, line.endPoint) && isSamePoint(history.endPoint, line.startPoint)) + ) + }) + mousePointerArr.current = [] clear() + + if (hasSameLine) { + canvas.remove(line) + return + } + + lineHistory.current.push(line) } const mouseDown = (e) => { @@ -520,8 +549,24 @@ export function useAuxiliaryDrawing(id) { otherAdsorptionPoints.push(intersectionPoint) }) }) + let innerLinePoints = [] + canvas + .getObjects() + .filter((obj) => obj.innerLines) + .forEach((polygon) => { + polygon.innerLines.forEach((line) => { + innerLinePoints.push({ x: line.x1, y: line.y1 }) + innerLinePoints.push({ x: line.x2, y: line.y2 }) + }) + }) - const adsorptionPoints = [...getAdsorptionPoints(), ...roofAdsorptionPoints.current, ...otherAdsorptionPoints, ...intersectionPoints.current] + const adsorptionPoints = [ + ...getAdsorptionPoints(), + ...roofAdsorptionPoints.current, + ...otherAdsorptionPoints, + ...intersectionPoints.current, + ...innerLinePoints, + ] let arrivalPoint = { x: pointer.x, y: pointer.y } @@ -780,7 +825,6 @@ export function useAuxiliaryDrawing(id) { //lineHistory.current에 있는 선들 중 startPoint와 endPoint가 겹치는 line은 제거 // 겹치는 선 하나는 canvas에서 제거한다. - const tempLines = [...lineHistory.current] lineHistory.current = [] tempLines.forEach((line) => { @@ -804,27 +848,30 @@ export function useAuxiliaryDrawing(id) { const tempPolygonPoints = [...roofBase.points].map((obj) => { return { x: Math.round(obj.x), y: Math.round(obj.y) } }) - const roofInnerLines = innerLines.filter((line) => { + const roofInnerLines = [...roofBase.innerLines, ...innerLines].filter((line) => { const inPolygon1 = - tempPolygonPoints.some((point) => point.x === line.x1 && point.y === line.y1) || - roofBase.inPolygon({ x: line.x1, y: line.y1 }) || - roofBase.lines.some((line) => isPointOnLine(line, { x: line.x1, y: line.y1 })) + tempPolygonPoints.some((point) => Math.round(point.x) === Math.round(line.x1) && Math.round(point.y) === Math.round(line.y1)) || + roofBase.inPolygon({ x: Math.round(line.x1), y: Math.round(line.y1) }) || + roofBase.lines.some((line) => isPointOnLine(line, { x: Math.round(line.x1), y: Math.round(line.y1) })) const inPolygon2 = - tempPolygonPoints.some((point) => point.x === line.x2 && point.y === line.y2) || - roofBase.inPolygon({ x: line.x2, y: line.y2 }) || - roofBase.lines.some((line) => isPointOnLine(line, { x: line.x2, y: line.y2 })) + tempPolygonPoints.some((point) => Math.round(point.x) === Math.round(line.x2) && Math.round(point.y) === Math.round(line.y2)) || + roofBase.inPolygon({ x: Math.round(line.x2), y: Math.round(line.y2) }) || + roofBase.lines.some((line) => isPointOnLine(line, { x: Math.round(line.x2), y: Math.round(line.y2) })) if (inPolygon1 && inPolygon2) { - line.attributes = { ...line.attributes, roofId: roofBase.id, actualSize: 0, planeSize: line.getLength() } + line.attributes = { + ...line.attributes, + roofId: roofBase.id, + actualSize: line.attributes?.actualSize ?? 0, + planeSize: line.getLength(), + } return true } }) - roofBase.innerLines = [...roofInnerLines] - + roofBase.innerLines = lineHistory.current.length !== 0 ? [...roofInnerLines] : roofBase.innerLines canvas.renderAll() }) - closePopup(id) } @@ -856,5 +903,8 @@ export function useAuxiliaryDrawing(id) { handleRollback, buttonAct, setButtonAct, + move, + copy, + cutAuxiliary, } } diff --git a/src/hooks/roofcover/useEavesGableEdit.js b/src/hooks/roofcover/useEavesGableEdit.js index 5619abfb..27e00a86 100644 --- a/src/hooks/roofcover/useEavesGableEdit.js +++ b/src/hooks/roofcover/useEavesGableEdit.js @@ -16,6 +16,7 @@ export function useEavesGableEdit(id) { const canvas = useRecoilValue(canvasState) const { getMessage } = useMessage() const { addCanvasMouseEventListener, initEvent } = useEvent() + // const { addCanvasMouseEventListener, initEvent } = useContext(EventContext) const { closePopup } = usePopup() const TYPES = { EAVES: 'eaves', diff --git a/src/hooks/roofcover/useMovementSetting.js b/src/hooks/roofcover/useMovementSetting.js index 34961ea0..509c53f3 100644 --- a/src/hooks/roofcover/useMovementSetting.js +++ b/src/hooks/roofcover/useMovementSetting.js @@ -6,7 +6,6 @@ import { useEffect, useRef, useState } from 'react' import { useEvent } from '@/hooks/useEvent' import { POLYGON_TYPE } from '@/common/common' import { OUTER_LINE_TYPE } from '@/store/outerLineAtom' -import { QLine } from '@/components/fabric/QLine' //동선이동 형 올림 내림 export function useMovementSetting(id) { @@ -16,6 +15,7 @@ export function useMovementSetting(id) { } const canvas = useRecoilValue(canvasState) const { initEvent, addCanvasMouseEventListener } = useEvent() + // const { initEvent, addCanvasMouseEventListener } = useContext(EventContext) const { closePopup } = usePopup() const { getMessage } = useMessage() const currentObject = useRecoilValue(currentObjectState) diff --git a/src/hooks/roofcover/useOuterLineWall.js b/src/hooks/roofcover/useOuterLineWall.js index b2244da8..c668dd60 100644 --- a/src/hooks/roofcover/useOuterLineWall.js +++ b/src/hooks/roofcover/useOuterLineWall.js @@ -1,6 +1,6 @@ -import { useEffect, useRef } from 'react' +import { useContext, useEffect, useRef } from 'react' import { distanceBetweenPoints } from '@/util/canvas-util' -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' +import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil' import { adsorptionPointAddModeState, adsorptionPointModeState, @@ -31,6 +31,7 @@ import { fabric } from 'fabric' import { outlineDisplaySelector } from '@/store/settingAtom' import { usePopup } from '@/hooks/usePopup' import PropertiesSetting from '@/components/floor-plan/modal/outerlinesetting/PropertiesSetting' +import { EventContext } from '@/app/floor-plan/EventProvider' //외벽선 그리기 export function useOuterLineWall(id, propertiesId) { @@ -43,6 +44,14 @@ export function useOuterLineWall(id, propertiesId) { removeAllDocumentEventListeners, removeMouseEvent, } = useEvent() + // const { + // initEvent, + // // addCanvasMouseEventListener, + // // addDocumentEventListener, + // removeAllMouseEventListeners, + // removeAllDocumentEventListeners, + // removeMouseEvent, + // } = useContext(EventContext) const { getIntersectMousePoint } = useMouse() const { addLine, removeLine } = useLine() const { tempGridMode } = useTempGrid() @@ -65,6 +74,7 @@ export function useOuterLineWall(id, propertiesId) { const [arrow1, setArrow1] = useRecoilState(outerLineArrow1State) const [arrow2, setArrow2] = useRecoilState(outerLineArrow2State) const [points, setPoints] = useRecoilState(outerLinePointsState) + const resetPoints = useResetRecoilState(outerLinePointsState) const [type, setType] = useRecoilState(outerLineTypeState) const [angle1, setAngle1] = useRecoilState(outerLineAngle1State) const [angle2, setAngle2] = useRecoilState(outerLineAngle2State) @@ -88,6 +98,13 @@ export function useOuterLineWall(id, propertiesId) { clear() return () => { initEvent() + + canvas + .getObjects() + .filter((obj) => obj.name === 'startPoint') + .forEach((obj) => { + canvas.remove(obj) + }) } }, [verticalHorizontalMode, points, adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, interval, tempGridMode]) diff --git a/src/hooks/roofcover/usePropertiesSetting.js b/src/hooks/roofcover/usePropertiesSetting.js index 3a1535a3..73a72e5a 100644 --- a/src/hooks/roofcover/usePropertiesSetting.js +++ b/src/hooks/roofcover/usePropertiesSetting.js @@ -125,6 +125,12 @@ export function usePropertiesSetting(id) { } const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + const notSetAttributes = lines.filter((line) => !line.attributes?.type) + if (notSetAttributes.length > 0) { + alert('설정되지 않은 외벽선이 있습니다.') + return + } + lines.forEach((line) => { line.set({ attributes: line.attributes ? line.attributes : { offset: 0, type: LINE_TYPE.WALLLINE.WALL }, diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js index b4118dc1..ebb0184a 100644 --- a/src/hooks/roofcover/useRoofAllocationSetting.js +++ b/src/hooks/roofcover/useRoofAllocationSetting.js @@ -1,5 +1,5 @@ -import { useRecoilValue } from 'recoil' -import { canvasState, currentObjectState } from '@/store/canvasAtom' +import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' +import { canvasState, currentMenuState, currentObjectState } from '@/store/canvasAtom' import { useEffect, useState } from 'react' import { setSurfaceShapePattern } from '@/util/canvas-util' import { useSwal } from '@/hooks/useSwal' @@ -10,17 +10,22 @@ 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 { menuTypeState } from '@/store/menuAtom' // 지붕면 할당 export function useRoofAllocationSetting(id) { const canvas = useRecoilValue(canvasState) const roofDisplay = useRecoilValue(roofDisplaySelector) - const { drawDirectionArrow, addLengthText, splitPolygonWithLines } = usePolygon() + const { drawDirectionArrow, addLengthText, splitPolygonWithLines, splitPolygonWithSeparate } = usePolygon() const [popupId, setPopupId] = useState(uuidv4()) const { addPopup, closePopup, closeAll } = usePopup() const { getMessage } = useMessage() const currentObject = useRecoilValue(currentObjectState) const { swalFire } = useSwal() + const { setMenuNumber } = useCanvasMenu() + const setMenuType = useSetRecoilState(menuTypeState) const roofMaterials = [ { id: 'A', @@ -107,7 +112,7 @@ export function useRoofAllocationSetting(id) { } }) }) - if (currentObject && currentObject.name && !currentObject.name.toLowerCase().includes('text')) { + if (currentObject && currentObject.name && ['auxiliaryLine', 'ridge', 'hip'].includes(currentObject.name)) { currentObject.set({ strokeWidth: 4, stroke: '#EA10AC', @@ -132,6 +137,9 @@ export function useRoofAllocationSetting(id) { setValues(values.filter((value) => value.id !== id)) } + const { handleMenu } = useMenu() + const [currentMenu, setCurrentMenu] = useRecoilState(currentMenuState) + // 선택한 지붕재로 할당 const handleSave = () => { // 모두 actualSize 있으면 바로 적용 없으면 actualSize 설정 @@ -176,20 +184,23 @@ export function useRoofAllocationSetting(id) { } const apply = () => { - const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) + const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF && !obj.isFixed) const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) roofBases.forEach((roofBase) => { try { - splitPolygonWithLines(roofBase) + if (roofBase.separatePolygon.length > 0) { + splitPolygonWithSeparate(roofBase.separatePolygon) + } else { + splitPolygonWithLines(roofBase) + } } catch (e) { return } - roofBase.innerLines.forEach((line) => { canvas.remove(line) }) - // canvas.remove(roofBase) + canvas.remove(roofBase) }) wallLines.forEach((wallLine) => { @@ -199,6 +210,10 @@ export function useRoofAllocationSetting(id) { const roofs = canvas.getObjects().filter((obj) => obj.name === 'roof') roofs.forEach((roof) => { + if (roof.isFixed) return + roof.set({ + isFixed: true, + }) setSurfaceShapePattern(roof, roofDisplay.column) drawDirectionArrow(roof) }) @@ -209,6 +224,8 @@ export function useRoofAllocationSetting(id) { }) setEditingLines([]) closeAll() + setMenuNumber(3) + setMenuType('surface') } const setLineSize = (id, size) => { diff --git a/src/hooks/roofcover/useRoofShapePassivitySetting.js b/src/hooks/roofcover/useRoofShapePassivitySetting.js index e0af3205..a2c799f4 100644 --- a/src/hooks/roofcover/useRoofShapePassivitySetting.js +++ b/src/hooks/roofcover/useRoofShapePassivitySetting.js @@ -26,6 +26,7 @@ export function useRoofShapePassivitySetting(id) { const { showLine, hideLine, addPitchTextsByOuterLines } = useLine() const { swalFire } = useSwal() const { addCanvasMouseEventListener, initEvent } = useEvent() + // const { addCanvasMouseEventListener, initEvent } = useContext(EventContext) const { drawRoofPolygon } = useMode() const { addPolygonByLines } = usePolygon() const currentObject = useRecoilValue(currentObjectState) @@ -202,7 +203,11 @@ export function useRoofShapePassivitySetting(id) { wall = addPolygonByLines(lines, { name: POLYGON_TYPE.WALL, fill: 'transparent', stroke: 'black' }) } else { // 그냥 닫을 경우 처리 - wall = addPolygonByLines([...initLines.current], { name: POLYGON_TYPE.WALL, fill: 'transparent', stroke: 'black' }) + wall = addPolygonByLines([...initLines.current], { + name: POLYGON_TYPE.WALL, + fill: 'transparent', + stroke: 'black', + }) lines.forEach((line, idx) => { line.attributes = initLines.current[idx].attributes }) diff --git a/src/hooks/roofcover/useRoofShapeSetting.js b/src/hooks/roofcover/useRoofShapeSetting.js index af55e557..c26c61ec 100644 --- a/src/hooks/roofcover/useRoofShapeSetting.js +++ b/src/hooks/roofcover/useRoofShapeSetting.js @@ -377,20 +377,20 @@ export function useRoofShapeSetting(id) { } // 기존 wallLine, roofBase 제거 - /*canvas + canvas .getObjects() .filter((obj) => obj.name === POLYGON_TYPE.WALL) .forEach((line) => { canvas.remove(line) - })*/ + }) - /*canvas + canvas .getObjects() - .filter((obj) => obj.name === POLYGON_TYPE.ROOF) + .filter((obj) => obj.name === POLYGON_TYPE.ROOF && !obj.isFixed) .forEach((obj) => { canvas.remove(...obj.innerLines) canvas.remove(obj) - })*/ + }) const polygon = addPolygonByLines(outerLines, { name: POLYGON_TYPE.WALL, direction }) polygon.lines = [...outerLines] diff --git a/src/hooks/roofcover/useWallLineOffsetSetting.js b/src/hooks/roofcover/useWallLineOffsetSetting.js index ba6453ef..22af0f49 100644 --- a/src/hooks/roofcover/useWallLineOffsetSetting.js +++ b/src/hooks/roofcover/useWallLineOffsetSetting.js @@ -15,6 +15,7 @@ export function useWallLineOffsetSetting(id) { const { closePopup } = usePopup() const { swalFire } = useSwal() const { addCanvasMouseEventListener, initEvent } = useEvent() + // const { addCanvasMouseEventListener, initEvent } = useContext(EventContext) const wallLineEditRef = useRef(null) const length1Ref = useRef(null) const length2Ref = useRef(null) diff --git a/src/hooks/surface/usePlacementShapeDrawing.js b/src/hooks/surface/usePlacementShapeDrawing.js index 24f641fe..95388ca4 100644 --- a/src/hooks/surface/usePlacementShapeDrawing.js +++ b/src/hooks/surface/usePlacementShapeDrawing.js @@ -33,12 +33,15 @@ import { POLYGON_TYPE } from '@/common/common' import { usePopup } from '@/hooks/usePopup' import { roofDisplaySelector } from '@/store/settingAtom' + // 면형상 배치 export function usePlacementShapeDrawing(id) { const canvas = useRecoilValue(canvasState) const roofDisplay = useRecoilValue(roofDisplaySelector) const { addCanvasMouseEventListener, addDocumentEventListener, removeAllMouseEventListeners, removeAllDocumentEventListeners, removeMouseEvent } = useEvent() + // const { addCanvasMouseEventListener, addDocumentEventListener, removeAllMouseEventListeners, removeAllDocumentEventListeners, removeMouseEvent } = + // useContext(EventContext) const { getIntersectMousePoint } = useMouse() const { addLine, removeLine } = useLine() const { addPolygonByLines, drawDirectionArrow } = usePolygon() @@ -429,56 +432,104 @@ export function usePlacementShapeDrawing(id) { if (prev.length === 0) { return [] } - return [...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10 }] + return [ + ...prev, + { + x: prev[prev.length - 1].x + length1Value / 10, + y: prev[prev.length - 1].y + length2Value / 10, + }, + ] }) } else if (arrow1Value === '↓' && arrow2Value === '←') { setPoints((prev) => { if (prev.length === 0) { return [] } - return [...prev, { x: prev[prev.length - 1].x - length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10 }] + return [ + ...prev, + { + x: prev[prev.length - 1].x - length1Value / 10, + y: prev[prev.length - 1].y + length2Value / 10, + }, + ] }) } else if (arrow1Value === '↑' && arrow2Value === '→') { setPoints((prev) => { if (prev.length === 0) { return [] } - return [...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y - length2Value / 10 }] + return [ + ...prev, + { + x: prev[prev.length - 1].x + length1Value / 10, + y: prev[prev.length - 1].y - length2Value / 10, + }, + ] }) } else if (arrow1Value === '↑' && arrow2Value === '←') { setPoints((prev) => { if (prev.length === 0) { return [] } - return [...prev, { x: prev[prev.length - 1].x - length1Value / 10, y: prev[prev.length - 1].y - length2Value / 10 }] + return [ + ...prev, + { + x: prev[prev.length - 1].x - length1Value / 10, + y: prev[prev.length - 1].y - length2Value / 10, + }, + ] }) } else if (arrow1Value === '→' && arrow2Value === '↓') { setPoints((prev) => { if (prev.length === 0) { return [] } - return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }] + return [ + ...prev, + { + x: prev[prev.length - 1].x + length2Value / 10, + y: prev[prev.length - 1].y + length1Value / 10, + }, + ] }) } else if (arrow1Value === '→' && arrow2Value === '↑') { setPoints((prev) => { if (prev.length === 0) { return [] } - return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }] + return [ + ...prev, + { + x: prev[prev.length - 1].x + length2Value / 10, + y: prev[prev.length - 1].y - length1Value / 10, + }, + ] }) } else if (arrow1Value === '←' && arrow2Value === '↓') { setPoints((prev) => { if (prev.length === 0) { return [] } - return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }] + return [ + ...prev, + { + x: prev[prev.length - 1].x - length2Value / 10, + y: prev[prev.length - 1].y + length1Value / 10, + }, + ] }) } else if (arrow1Value === '←' && arrow2Value === '↑') { setPoints((prev) => { if (prev.length === 0) { return [] } - return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }] + return [ + ...prev, + { + x: prev[prev.length - 1].x - length2Value / 10, + y: prev[prev.length - 1].y - length1Value / 10, + }, + ] }) } @@ -534,35 +585,65 @@ export function usePlacementShapeDrawing(id) { if (prev.length === 0) { return [] } - return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }] + return [ + ...prev, + { + x: prev[prev.length - 1].x + length2Value / 10, + y: prev[prev.length - 1].y + length1Value / 10, + }, + ] }) } else if (arrow1Value === '↓' && arrow2Value === '←') { setPoints((prev) => { if (prev.length === 0) { return [] } - return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }] + return [ + ...prev, + { + x: prev[prev.length - 1].x - length2Value / 10, + y: prev[prev.length - 1].y + length1Value / 10, + }, + ] }) } else if (arrow1Value === '↑' && arrow2Value === '→') { setPoints((prev) => { if (prev.length === 0) { return [] } - return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }] + return [ + ...prev, + { + x: prev[prev.length - 1].x + length2Value / 10, + y: prev[prev.length - 1].y - length1Value / 10, + }, + ] }) } else if (arrow1Value === '↑' && arrow2Value === '←') { setPoints((prev) => { if (prev.length === 0) { return [] } - return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }] + return [ + ...prev, + { + x: prev[prev.length - 1].x - length2Value / 10, + y: prev[prev.length - 1].y - length1Value / 10, + }, + ] }) } else if (arrow1Value === '→' && arrow2Value === '↓') { setPoints((prev) => { if (prev.length === 0) { return [] } - return [...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10 }] + return [ + ...prev, + { + x: prev[prev.length - 1].x + length1Value / 10, + y: prev[prev.length - 1].y + length2Value / 10, + }, + ] }) } else if (arrow1Value === '→' && arrow2Value === '↑') { setPoints((prev) => { diff --git a/src/hooks/surface/useSurfaceShapeBatch.js b/src/hooks/surface/useSurfaceShapeBatch.js index 67ee1874..844829d3 100644 --- a/src/hooks/surface/useSurfaceShapeBatch.js +++ b/src/hooks/surface/useSurfaceShapeBatch.js @@ -2,7 +2,7 @@ import { useRecoilValue } from 'recoil' import { canvasState, globalPitchState } from '@/store/canvasAtom' -import { MENU, BATCH_TYPE, POLYGON_TYPE } from '@/common/common' +import { MENU, POLYGON_TYPE } from '@/common/common' import { getIntersectionPoint, setSurfaceShapePattern } from '@/util/canvas-util' import { degreesToRadians } from '@turf/turf' import { QPolygon } from '@/components/fabric/QPolygon' @@ -27,6 +27,7 @@ export function useSurfaceShapeBatch() { const slope = useRecoilValue(slopeSelector(globalPitch)) const { swalFire } = useSwal() const { addCanvasMouseEventListener, initEvent } = useEvent() + // const { addCanvasMouseEventListener, initEvent } = useContext(EventContext) const { closePopup } = usePopup() const applySurfaceShape = (surfaceRefs, selectedType, id) => { @@ -102,6 +103,15 @@ export function useSurfaceShapeBatch() { canvas?.add(obj) + canvas?.renderAll() + closePopup(id) + }) + + addCanvasMouseEventListener('mouse:down', (e) => { + isDrawing = false + + canvas?.remove(obj) + //각도 추가 let originAngle = 0 //기본 남쪽 let direction = 'south' @@ -119,21 +129,31 @@ export function useSurfaceShapeBatch() { direction = 'north' } - obj.set({ direction: direction }) - obj.set({ originAngle: originAngle }) - - canvas?.renderAll() + //회전, flip등이 먹은 기준으로 새로생성 + const batchSurface = new QPolygon(obj.getCurrentPoints(), { + fill: 'transparent', + stroke: 'red', + strokeWidth: 1, + strokeDasharray: [10, 4], + fontSize: 12, + selectable: true, + lockMovementX: true, // X 축 이동 잠금 + lockMovementY: true, // Y 축 이동 잠금 + lockRotation: true, // 회전 잠금 + lockScalingX: true, // X 축 크기 조정 잠금 + lockScalingY: true, // Y 축 크기 조정 잠금 + name: POLYGON_TYPE.ROOF, + originX: 'center', + originY: 'center', + pitch: globalPitch, + surfaceId: surfaceId, + direction: direction, + }) + canvas?.add(batchSurface) + setSurfaceShapePattern(batchSurface, roofDisplay.column) + drawDirectionArrow(batchSurface) closePopup(id) - }) - - addCanvasMouseEventListener('mouse:down', (e) => { - isDrawing = false - obj.set('name', POLYGON_TYPE.ROOF) - obj.set('surfaceId', surfaceId) initEvent() - setSurfaceShapePattern(obj, roofDisplay.column) - closePopup(id) - drawDirectionArrow(obj) }) } } @@ -307,8 +327,14 @@ export function useSurfaceShapeBatch() { const angleInRadians = Math.asin(length2 / length3) points = [ { x: pointer.x - length1 / 2, y: pointer.y + length2 / 2 }, - { x: pointer.x - length1 / 2 + length3 * Math.cos(angleInRadians), y: pointer.y + length2 / 2 - length3 * Math.sin(angleInRadians) }, - { x: pointer.x + length1 / 2 + length3 * Math.cos(angleInRadians), y: pointer.y + length2 / 2 - length3 * Math.sin(angleInRadians) }, + { + x: pointer.x - length1 / 2 + length3 * Math.cos(angleInRadians), + y: pointer.y + length2 / 2 - length3 * Math.sin(angleInRadians), + }, + { + x: pointer.x + length1 / 2 + length3 * Math.cos(angleInRadians), + y: pointer.y + length2 / 2 - length3 * Math.sin(angleInRadians), + }, { x: pointer.x + length1 / 2, y: pointer.y + length2 / 2 }, ] @@ -319,9 +345,18 @@ export function useSurfaceShapeBatch() { { x: pointer.x - (length1 + length2 + length3) / 2, y: pointer.y - (length4 + length5) / 2 }, { x: pointer.x - (length1 + length2 + length3) / 2, y: pointer.y + (length4 + length5) / 2 }, { x: pointer.x - (length1 + length2 + length3) / 2 + length1, y: pointer.y + (length4 + length5) / 2 }, - { x: pointer.x - (length1 + length2 + length3) / 2 + length1, y: pointer.y + (length4 + length5) / 2 - length5 }, - { x: pointer.x - (length1 + length2 + length3) / 2 + length1 + length2, y: pointer.y + (length4 + length5) / 2 - length5 }, - { x: pointer.x - (length1 + length2 + length3) / 2 + length1 + length2, y: pointer.y + (length4 + length5) / 2 - length5 + length5 }, + { + x: pointer.x - (length1 + length2 + length3) / 2 + length1, + y: pointer.y + (length4 + length5) / 2 - length5, + }, + { + x: pointer.x - (length1 + length2 + length3) / 2 + length1 + length2, + y: pointer.y + (length4 + length5) / 2 - length5, + }, + { + x: pointer.x - (length1 + length2 + length3) / 2 + length1 + length2, + y: pointer.y + (length4 + length5) / 2 - length5 + length5, + }, { x: pointer.x - (length1 + length2 + length3) / 2 + length1 + length2 + length3, y: pointer.y + (length4 + length5) / 2 - length5 + length5, @@ -340,8 +375,14 @@ export function useSurfaceShapeBatch() { { x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 }, { x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length3 }, { x: pointer.x - length1 / 2 + length1 - (length1 - length2), y: pointer.y + length4 / 2 - length3 }, - { x: pointer.x - length1 / 2 + length1 - (length1 - length2), y: pointer.y + length4 / 2 - length3 + (length3 - length4) }, - { x: pointer.x - length1 / 2 + length1 - (length1 - length2) - length2, y: pointer.y + length4 / 2 - length3 + (length3 - length4) }, + { + x: pointer.x - length1 / 2 + length1 - (length1 - length2), + y: pointer.y + length4 / 2 - length3 + (length3 - length4), + }, + { + x: pointer.x - length1 / 2 + length1 - (length1 - length2) - length2, + y: pointer.y + length4 / 2 - length3 + (length3 - length4), + }, ] break @@ -352,8 +393,14 @@ export function useSurfaceShapeBatch() { { x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 }, { x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length4 }, { x: pointer.x - length1 / 2 + length1 - length3, y: pointer.y + length4 / 2 - length4 }, - { x: pointer.x - length1 / 2 + length1 - length3, y: pointer.y + length4 / 2 - length4 + (length4 - length5) }, - { x: pointer.x - length1 / 2 + length1 - length3 - length2, y: pointer.y + length4 / 2 - length4 + (length4 - length5) }, + { + x: pointer.x - length1 / 2 + length1 - length3, + y: pointer.y + length4 / 2 - length4 + (length4 - length5), + }, + { + x: pointer.x - length1 / 2 + length1 - length3 - length2, + y: pointer.y + length4 / 2 - length4 + (length4 - length5), + }, ] break } @@ -363,8 +410,14 @@ export function useSurfaceShapeBatch() { { x: pointer.x + length1 / 2 - length1, y: pointer.y + length4 / 2 }, { x: pointer.x + length1 / 2 - length1, y: pointer.y + length4 / 2 - length5 }, { x: pointer.x + length1 / 2 - length1 + length2, y: pointer.y + length4 / 2 - length5 }, - { x: pointer.x + length1 / 2 - length1 + length2, y: pointer.y + length4 / 2 - length5 - (length4 - length5) }, - { x: pointer.x + length1 / 2 - length1 + length2 + length3, y: pointer.y + length4 / 2 - length5 - (length4 - length5) }, + { + x: pointer.x + length1 / 2 - length1 + length2, + y: pointer.y + length4 / 2 - length5 - (length4 - length5), + }, + { + x: pointer.x + length1 / 2 - length1 + length2 + length3, + y: pointer.y + length4 / 2 - length5 - (length4 - length5), + }, ] break } @@ -374,8 +427,14 @@ export function useSurfaceShapeBatch() { { x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 }, { x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length5 }, { x: pointer.x - length1 / 2 + length1 - length2, y: pointer.y + length4 / 2 - length5 }, - { x: pointer.x - length1 / 2 + length1 - length2, y: pointer.y + length4 / 2 - length5 - (length4 - length5) }, - { x: pointer.x - length1 / 2 + length1 - length2 - length3, y: pointer.y + length4 / 2 - length5 - (length4 - length5) }, + { + x: pointer.x - length1 / 2 + length1 - length2, + y: pointer.y + length4 / 2 - length5 - (length4 - length5), + }, + { + x: pointer.x - length1 / 2 + length1 - length2 - length3, + y: pointer.y + length4 / 2 - length5 - (length4 - length5), + }, ] break } @@ -386,7 +445,10 @@ export function useSurfaceShapeBatch() { const leftAngle = Math.acos((length1 - length2) / 2 / leftHypotenuse) points = [ - { x: pointer.x - length1 / 2 + leftHypotenuse * Math.cos(leftAngle), y: pointer.y + length3 / 2 - leftHypotenuse * Math.sin(leftAngle) }, + { + x: pointer.x - length1 / 2 + leftHypotenuse * Math.cos(leftAngle), + y: pointer.y + length3 / 2 - leftHypotenuse * Math.sin(leftAngle), + }, { x: pointer.x - length1 / 2, y: pointer.y + length3 / 2 }, { x: pointer.x + length1 / 2, y: pointer.y + length3 / 2 }, { @@ -419,8 +481,8 @@ export function useSurfaceShapeBatch() { { fill: 'transparent', stroke: 'black', //black - strokeWidth: 2, - selectable: true, + strokeWidth: 1, + selectable: false, fontSize: 0, }, ) @@ -429,6 +491,7 @@ export function useSurfaceShapeBatch() { const scale = (length1 - length2) / coord.x tmpPolygon.set({ scaleX: scale }) + tmpPolygon.setViewLengthText(false) pointsArray[0].x = 0 pointsArray[0].y = length3 //바닥면부터 시작하게 @@ -474,7 +537,10 @@ export function useSurfaceShapeBatch() { { x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length4 }, { x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length4 + length4 }, { x: pointer.x - length1 / 2 + length1 - length3, y: pointer.y + length4 / 2 - length4 + length4 }, - { x: pointer.x - length1 / 2 + length2 + (length1 - length2 - length3) / 2, y: pointer.y + length4 / 2 - length4 + length5 }, + { + x: pointer.x - length1 / 2 + length2 + (length1 - length2 - length3) / 2, + y: pointer.y + length4 / 2 - length4 + length5, + }, ] break } @@ -584,18 +650,6 @@ export function useSurfaceShapeBatch() { text: '배치면 내용을 전부 삭제하시겠습니까?', type: 'confirm', confirmFn: () => { - // canvas?.getObjects().forEach((obj) => { - // if ( - // obj.name === POLYGON_TYPE.ROOF || - // obj.name === BATCH_TYPE.OPENING || - // obj.name === BATCH_TYPE.SHADOW || - // obj.name === BATCH_TYPE.TRIANGLE_DORMER || - // obj.name === BATCH_TYPE.PENTAGON_DORMER || - // obj.name === 'lengthText' - // ) { - // canvas?.remove(obj) - // } - // }) canvas.clear() swalFire({ text: '삭제 완료 되었습니다.' }) }, @@ -661,34 +715,6 @@ export function useSurfaceShapeBatch() { return groupObjectsArray } - function getAllRelatedObjects(id) { - const ult = [] - const map = new Map() - - // Create a map of objects by their id - canvas.getObjects().forEach((obj) => { - map.set(obj.id, obj) - }) - - // Helper function to recursively find all related objects - function findRelatedObjects(id) { - const obj = map.get(id) - if (obj) { - result.push(obj) - canvas.getObjects().forEach((o) => { - if (o.parentId === id) { - findRelatedObjects(o.id) - } - }) - } - } - - // Start the search with the given parentId - findRelatedObjects(id) - - return result - } - const moveSurfaceShapeBatch = () => { const roof = canvas.getActiveObject() @@ -828,136 +854,75 @@ export function useSurfaceShapeBatch() { canvas.renderAll() } - const surfaceShapeActualSize = () => { - const roofs = canvas.getObjects().filter((obj) => obj.name === 'roof') - let notParallelLines = [] - - const roofArray = [] - - roofs.forEach((obj) => { - const roof = { - id: obj.id, - surfaceId: obj.surfaceId, - targetLines: [], - baseLines: [], - direction: obj.direction, - } - obj.lines.forEach((line) => { - if (obj.direction === 'north' || obj.direction === 'south') { - if (line.y1 !== line.y2) { - roof.targetLines.push(line) - } else { - roof.baseLines.push(line) - } - } else { - if (line.x1 !== line.x2) { - roof.targetLines.push(line) - } else { - roof.baseLines.push(line) - } - } - }) - roofArray.push(roof) - }) - - const slopeRadians = slope.angleValue * (Math.PI / 180) - const tanSlope = Math.tan(slopeRadians) - - roofArray.forEach((roof) => { - roof.targetLines.forEach((line) => { - let calcLength = line.length - - if (roof.surfaceId === 1) { - calcLength = roof.direction === 'north' || roof.direction === 'south' ? Math.abs(line.y1 - line.y2) : Math.abs(line.x1 - line.x2) - } - - const h = calcLength * tanSlope - const resultLength = Math.sqrt(Math.pow(calcLength, 2) + Math.pow(h, 2)) - - line.set({ - attributes: { - ...line.attributes, - actualSize: parseInt((Math.floor(resultLength) * 10).toFixed(0)), - planeSize: parseInt(line.length * 10), - }, - }) - }) - roof.baseLines.forEach((line) => { - line.set({ - attributes: { - ...line.attributes, - actualSize: parseInt((Math.floor(line.length) * 10).toFixed(0)), - planeSize: parseInt(line.length * 10), - }, - }) - }) - }) - } - - const changeSurfaceLinePropertyEvent = (roof) => { + const changeSurfaceLinePropertyEvent = () => { let tmpLines = [] - roof.set({ - selectable: false, - }) - canvas.discardActiveObject() - roof.lines.forEach((obj, index) => { - const tmpLine = new QLine([obj.x1, obj.y1, obj.x2, obj.y2], { - ...obj, - stroke: 'rgb(3, 255, 0)', - strokeWidth: 8, - selectable: true, - name: 'lineProperty', - index: index, + const roof = canvas.getActiveObject() + + if (roof) { + roof.set({ + selectable: false, }) - tmpLines.push(tmpLine) - canvas.add(tmpLine) - }) - - addCanvasMouseEventListener('mouse:down', (e) => { - const selectedLine = e.target - if (selectedLine) { - selectedLine.set({ - stroke: 'red', - name: 'selectedLineProperty', + roof.lines.forEach((obj, index) => { + const tmpLine = new QLine([obj.x1, obj.y1, obj.x2, obj.y2], { + ...obj, + stroke: 'rgb(3, 255, 0)', + strokeWidth: 8, + selectable: true, + name: 'lineProperty', + lineIndex: index, }) - tmpLines.forEach((line) => { - if (line.index !== selectedLine.index) { + + tmpLines.push(tmpLine) + canvas.add(tmpLine) + }) + + addCanvasMouseEventListener('mouse:down', (e) => { + const selectedLine = e.target + if (selectedLine && selectedLine.name !== 'roof') { + tmpLines.forEach((line) => { line.set({ stroke: 'rgb(3, 255, 0)', name: 'lineProperty', }) - } - }) - } else { - tmpLines.forEach((line) => { - line.set({ - stroke: 'rgb(3, 255, 0)', - name: 'lineProperty', }) - }) - } - }) + selectedLine.set({ + stroke: 'red', + name: 'selectedLineProperty', + }) + } else { + tmpLines.forEach((line) => { + line.set({ + stroke: 'rgb(3, 255, 0)', + name: 'lineProperty', + }) + }) + } + }) - canvas.renderAll() + canvas.renderAll() + } + canvas.discardActiveObject() } - const changeSurfaceLineProperty = (property) => { - console.log(property) + const changeSurfaceLineProperty = (property, roof) => { if (!property) { swalFire({ text: getMessage('modal.line.property.change'), icon: 'error' }) return } - const selectedLine = canvas.getActiveObjects() - if (selectedLine && selectedLine[0].name === 'selectedLineProperty') { + const selectedLine = canvas.getActiveObjects()[0] //배열로 떨어짐 한개만 선택가능 + if (selectedLine && selectedLine.name === 'selectedLineProperty') { swalFire({ text: getMessage('modal.line.property.change.confirm'), type: 'confirm', confirmFn: () => { - selectedLine.set({ + const lineIndex = selectedLine.lineIndex + roof.lines[lineIndex].attributes = { + ...roof.lines[lineIndex].attributes, type: property.value, - }) + } + canvas.renderAll() }, }) } else { @@ -965,22 +930,70 @@ export function useSurfaceShapeBatch() { } } - const changeSurfaceFlowDirection = (roof, direction, orientation) => { - roof.set({ - direction: direction, + const changeSurfaceLinePropertyReset = (roof) => { + const lines = canvas.getObjects().filter((obj) => obj.name === 'lineProperty' || obj.name === 'selectedLineProperty') + + lines.forEach((line) => { + canvas.remove(line) }) - drawDirectionArrow(roof) + + if (roof) { + roof.set({ + selectable: true, + }) + } canvas?.renderAll() } + const updateFlippedPoints = (polygon) => { + if (!(polygon instanceof fabric.Polygon)) { + console.error('The object is not a Polygon.') + return + } + + const { flipX, flipY, width, height, points, left, top, scaleX, scaleY } = polygon + + // 현재 points의 사본 가져오기 + const newPoints = points.map((point) => { + let x = point.x + let y = point.y + + // flipX 적용 + if (flipX) { + x = width - x + } + + // flipY 적용 + if (flipY) { + y = height - y + } + + // 스케일 및 전역 좌표 고려 + x = (x - width / 2) * scaleX + width / 2 + y = (y - height / 2) * scaleY + height / 2 + + return { x, y } + }) + + // flipX, flipY를 초기화 + polygon.flipX = false + polygon.flipY = false + + // points 업데이트 + polygon.set({ points: newPoints }) + polygon.setCoords() + + return polygon + } + return { applySurfaceShape, deleteAllSurfacesAndObjects, moveSurfaceShapeBatch, resizeSurfaceShapeBatch, - surfaceShapeActualSize, - changeSurfaceFlowDirection, + changeSurfaceLinePropertyEvent, changeSurfaceLineProperty, + changeSurfaceLinePropertyReset, } } diff --git a/src/hooks/useCanvas.js b/src/hooks/useCanvas.js index 6ffb03fc..46dbbb35 100644 --- a/src/hooks/useCanvas.js +++ b/src/hooks/useCanvas.js @@ -109,11 +109,24 @@ export function useCanvas(id) { OBJECT_PROTOTYPE.forEach((type) => { type.toObject = function (propertiesToInclude) { let source = {} + for (let key in this) { if (typeof this[key] !== 'function' && SAVE_KEY.includes(key)) { source.key = this[key] } } + + //QLine에 커스텀 어트리뷰트 넣기 + if (this.type === 'QLine') { + if (this.attributes) { + this.attributes.type = this.attributes.type || 'default' + source.attributes = { + ...this.attributes, + type: this.attributes.type, + } + } + } + return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), source) } }) diff --git a/src/hooks/useCanvasEvent.js b/src/hooks/useCanvasEvent.js index 42266c8a..0e8cd64b 100644 --- a/src/hooks/useCanvasEvent.js +++ b/src/hooks/useCanvasEvent.js @@ -225,7 +225,9 @@ export function useCanvasEvent() { if (deselected?.length > 0) { deselected.forEach((obj) => { if (obj.type === 'QPolygon') { - obj.set({ stroke: 'black' }) + if (obj.name !== 'moduleSetupSurface') { + obj.set({ stroke: 'black' }) + } } }) } diff --git a/src/hooks/useContextMenu.js b/src/hooks/useContextMenu.js index 22e388db..f7f79f23 100644 --- a/src/hooks/useContextMenu.js +++ b/src/hooks/useContextMenu.js @@ -1,8 +1,7 @@ import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' -import { currentMenuState, currentObjectState } from '@/store/canvasAtom' +import { canvasState, currentMenuState, currentObjectState } from '@/store/canvasAtom' import { useEffect, useState } from 'react' import { MENU } from '@/common/common' -import AuxiliaryMove from '@/components/floor-plan/modal/auxiliary/AuxiliaryMove' import AuxiliarySize from '@/components/floor-plan/modal/auxiliary/AuxiliarySize' import { usePopup } from '@/hooks/usePopup' import { v4 as uuidv4 } from 'uuid' @@ -11,7 +10,7 @@ import GridCopy from '@/components/floor-plan/modal/grid/GridCopy' import ColorPickerModal from '@/components/common/color-picker/ColorPickerModal' import { gridColorState } from '@/store/gridAtom' import { contextPopupPositionState, contextPopupState } from '@/store/popupAtom' -import AuxiliaryCopy from '@/components/floor-plan/modal/auxiliary/AuxiliaryCopy' +import AuxiliaryEdit from '@/components/floor-plan/modal/auxiliary/AuxiliaryEdit' import SizeSetting from '@/components/floor-plan/modal/object/SizeSetting' import RoofMaterialSetting from '@/components/floor-plan/modal/object/RoofMaterialSetting' import DormerOffset from '@/components/floor-plan/modal/object/DormerOffset' @@ -24,7 +23,6 @@ import { useCommonUtils } from './common/useCommonUtils' import { useMessage } from '@/hooks/useMessage' import { useCanvasEvent } from '@/hooks/useCanvasEvent' import { contextMenuListState, contextMenuState } from '@/store/contextMenu' -import ImageSizeSetting from '@/components/floor-plan/modal/image/ImageSizeSetting' import PanelEdit from '@/components/floor-plan/modal/module/PanelEdit' import DimensionLineSetting from '@/components/floor-plan/modal/dimensionLine/DimensionLineSetting' import ColumnRemove from '@/components/floor-plan/modal/module/column/ColumnRemove' @@ -34,8 +32,12 @@ import RowInsert from '@/components/floor-plan/modal/module/row/RowInsert' import CircuitNumberEdit from '@/components/floor-plan/modal/module/CircuitNumberEdit' import { useObjectBatch } from '@/hooks/object/useObjectBatch' import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch' +import { fontSelector, globalFontAtom } from '@/store/fontAtom' +import { useLine } from '@/hooks/useLine' +import { useSwal } from '@/hooks/useSwal' export function useContextMenu() { + const canvas = useRecoilValue(canvasState) const currentMenu = useRecoilValue(currentMenuState) // 현재 메뉴 const setContextPopupPosition = useSetRecoilState(contextPopupPositionState) // 현재 메뉴 const [contextMenu, setContextMenu] = useRecoilState(contextMenuListState) // 메뉴.object 별 context menu @@ -51,7 +53,12 @@ export function useContextMenu() { const [column, setColumn] = useState(null) const { handleZoomClear } = useCanvasEvent() const { moveObjectBatch } = useObjectBatch({}) - const { moveSurfaceShapeBatch, surfaceShapeActualSize } = useSurfaceShapeBatch() + const { moveSurfaceShapeBatch } = useSurfaceShapeBatch() + const [globalFont, setGlobalFont] = useRecoilState(globalFontAtom) + const { addLine, removeLine } = useLine() + const commonTextFont = useRecoilValue(fontSelector('commonText')) + const { swalFire } = useSwal() + const currentMenuSetting = () => { switch (currentMenu) { case MENU.PLAN_DRAWING: @@ -95,11 +102,6 @@ export function useContextMenu() { case MENU.ROOF_COVERING.DEFAULT: setContextMenu([ [ - { - id: 'refresh', - name: getMessage('refresh'), - fn: () => handleZoomClear(), - }, { id: 'roofMaterialPlacement', name: getMessage('contextmenu.roof.material.placement'), @@ -121,11 +123,6 @@ export function useContextMenu() { name: getMessage('contextmenu.wallline.remove'), fn: () => deleteOuterLineObject(), }, - { - id: 'imageSizeEdit', - name: getMessage('modal.image.size.setting'), - component: , - }, ], [ { @@ -137,30 +134,81 @@ export function useContextMenu() { id: 'auxiliaryMove', name: `${getMessage('contextmenu.auxiliary.move')}(M)`, shortcut: ['m', 'M'], - component: , + component: , }, { id: 'auxiliaryCopy', name: `${getMessage('contextmenu.auxiliary.copy')}(C)`, shortcut: ['c', 'C'], - component: , + component: , }, { id: 'auxiliaryRemove', shortcut: ['d', 'D'], name: `${getMessage('contextmenu.auxiliary.remove')}(D)`, + fn: () => { + const roof = canvas.getObjects().filter((obj) => obj.id === currentObject.attributes.roofId)[0] + const innerLines = roof.innerLines?.filter((line) => currentObject.id !== line.id) + roof.innerLines = [...innerLines] + canvas.remove(currentObject) + canvas.discardActiveObject() + }, }, { id: 'auxiliaryVerticalBisector', name: getMessage('contextmenu.auxiliary.vertical.bisector'), - }, - { - id: 'auxiliaryCut', - name: getMessage('contextmenu.auxiliary.cut'), + fn: () => { + const slope = (currentObject.y2 - currentObject.y1) / (currentObject.x2 - currentObject.x1) + const length = currentObject.length + + let startX, startY, endX, endY + if (slope === 0) { + startX = endX = (currentObject.x1 + currentObject.x2) / 2 + startY = currentObject.y2 - length / 2 + endY = currentObject.y2 + length / 2 + } else if (slope === Infinity) { + startX = currentObject.x1 - length / 2 + startY = endY = (currentObject.y1 + currentObject.y2) / 2 + endX = currentObject.x1 + length / 2 + } else { + const bisectorSlope = -1 / slope + const dx = length / 2 / Math.sqrt(1 + bisectorSlope * bisectorSlope) + const dy = bisectorSlope * dx + + startX = (currentObject.x1 + currentObject.x2) / 2 + dx + startY = (currentObject.y1 + currentObject.y2) / 2 + dy + endX = (currentObject.x1 + currentObject.x2) / 2 - dx + endY = (currentObject.y1 + currentObject.y2) / 2 - dy + } + + const line = addLine([startX, startY, endX, endY], { + stroke: 'red', + strokeWidth: 1, + selectable: true, + name: 'auxiliaryLine', + attributes: { ...currentObject.attributes }, + }) + canvas + .getObjects() + .filter((obj) => obj.id === currentObject.attributes.roofId)[0] + .innerLines.push(line) + }, }, { id: 'auxiliaryRemoveAll', name: getMessage('contextmenu.auxiliary.remove.all'), + fn: () => { + if (!currentObject) { + swalFire({ text: '지붕을 선택해주세요.' }) + return + } + const innerLines = canvas.getObjects().filter((obj) => obj.id === currentObject.attributes.roofId)[0].innerLines + innerLines.forEach((line) => { + canvas.remove(line) + }) + innerLines.length = 0 + canvas.renderAll() + }, }, ], ]) @@ -193,16 +241,12 @@ export function useContextMenu() { shortcut: ['c', 'C'], name: `${getMessage('contextmenu.copy')}(C)`, }, - { - id: 'imageSizeEdit', - name: getMessage('modal.image.size.setting'), - component: , - }, ], [ { id: 'roofMaterialEdit', name: getMessage('contextmenu.roof.material.edit'), + component: , }, { id: 'linePropertyEdit', @@ -260,6 +304,7 @@ export function useContextMenu() { useEffect(() => { if (currentObject?.name) { + console.log('object', currentObject) switch (currentObject.name) { case 'triangleDormer': case 'pentagonDormer': @@ -304,11 +349,6 @@ export function useContextMenu() { case 'roof': setContextMenu([ [ - { - id: 'surfaceShapeActualSize', - name: '면형상 실측', - fn: () => surfaceShapeActualSize(), - }, { id: 'sizeEdit', name: '사이즈 변경', @@ -429,7 +469,26 @@ export function useContextMenu() { { id: 'commonTextFontSetting', name: getMessage('contextmenu.font.setting'), - component: , + component: ( + { + setGlobalFont((prev) => { + return { + ...prev, + commonText: { + fontFamily: font.fontFamily, + fontWeight: font.fontWeight, + fontSize: font.fontSize, + fontColor: font.fontColor, + }, + } + }) + }} + /> + ), }, { id: 'commonTextEdit', @@ -440,19 +499,24 @@ export function useContextMenu() { ]) break case 'lineGrid': + case 'dotGrid': + case 'tempGrid': setContextMenu([ [ { id: 'gridMove', name: getMessage('modal.grid.move'), + component: , }, { id: 'gridCopy', name: getMessage('modal.grid.copy'), + component: , }, { id: 'gridColorEdit', name: getMessage('contextmenu.grid.color.edit'), + component: , }, { id: 'remove', @@ -461,6 +525,7 @@ export function useContextMenu() { { id: 'removeAll', name: getMessage('contextmenu.remove.all'), + fn: () => {}, }, ], ]) diff --git a/src/hooks/useEvent.js b/src/hooks/useEvent.js index 891ad101..16d0896d 100644 --- a/src/hooks/useEvent.js +++ b/src/hooks/useEvent.js @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react' +import { useCallback, useEffect, useRef } from 'react' import { useRecoilValue, useSetRecoilState } from 'recoil' import { canvasState, canvasZoomState, currentMenuState, textModeState } from '@/store/canvasAtom' import { fabric } from 'fabric' @@ -219,6 +219,12 @@ export function useEvent() { mouseEventListeners.current.length = 0 // 배열 초기화 } + const addTargetMouseEventListener = (eventType, target, handler) => { + target.off(eventType) + target.on(eventType, handler) + mouseEventListeners.current.push({ eventType, handler }) + } + /** * document 이벤트의 경우 이 함수를 통해서만 등록 * @param eventType @@ -264,6 +270,7 @@ export function useEvent() { return { addDocumentEventListener, addCanvasMouseEventListener, + addTargetMouseEventListener, removeAllMouseEventListeners, removeAllDocumentEventListeners, removeDocumentEvent, diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index ce7b8210..6f6f3ad7 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -1265,7 +1265,6 @@ export function useMode() { const wall = makePolygon(null, sort) wall.name = 'wall' - // wall.set({ name: 'wall' }) return wall } @@ -1363,7 +1362,8 @@ export function useMode() { const polygon = new QPolygon( points, { - stroke: 'black', + stroke: '#1083E3', + strokeWidth: 2, fill: 'transparent', viewLengthText: true, fontSize: fontSize, @@ -1510,53 +1510,29 @@ export function useMode() { polygon.lines.forEach((line, index) => { line.attributes = { type: LINE_TYPE.WALLLINE.EAVES, - offset: 40, + offset: 50, width: 50, pitch: 4, sleeve: true, } - /*if (index === 1) { + /*if (index % 2 !== 0) { line.attributes = { - type: LINE_TYPE.WALLLINE.SHED, - offset: 30, //출폭 - width: 30, //폭 - pitch: 4, //구배 - sleeve: true, //소매 - } - } else if (index === 5 || index === 3) { - line.attributes = { - type: LINE_TYPE.WALLLINE.EAVES, - offset: 50, //출폭 - width: 30, //폭 - pitch: 4, //구배 - sleeve: true, //소매 + type: LINE_TYPE.WALLLINE.GABLE, + offset: 30, + width: 50, + pitch: 4, + sleeve: true, } } else { line.attributes = { - type: LINE_TYPE.WALLLINE.GABLE, - offset: 20, + type: LINE_TYPE.WALLLINE.EAVES, + offset: 50, width: 50, pitch: 4, sleeve: true, } }*/ - /* if (index === 1) { - line.attributes = { - type: LINE_TYPE.WALLLINE.SHED, - offset: 20, //출폭 - width: 30, //폭 - pitch: 4, //구배 - sleeve: true, //소매 - } - } else if (index === 3) { - line.attributes = { - type: LINE_TYPE.WALLLINE.EAVES, - offset: 50, //출폭 - width: 30, //폭 - pitch: 4, //구배 - sleeve: true, //소매 - } - } else { + /*if (index === polygon.lines.length - 1) { line.attributes = { type: LINE_TYPE.WALLLINE.GABLE, offset: 30, @@ -1564,6 +1540,14 @@ export function useMode() { pitch: 4, sleeve: true, } + } else { + line.attributes = { + type: LINE_TYPE.WALLLINE.EAVES, + offset: 50, + width: 50, + pitch: 4, + sleeve: true, + } }*/ }) @@ -1781,6 +1765,10 @@ export function useMode() { roofId: roof.id, } + canvas + .getObjects() + .filter((line) => line.attributes?.wallId === wall.id) + .forEach((line) => canvas.remove(line)) wall.lines.forEach((line, index) => { const lineLength = Math.sqrt( Math.pow(Math.round(Math.abs(line.x1 - line.x2) * 10), 2) + Math.pow(Math.round(Math.abs(line.y1 - line.y2) * 10), 2), @@ -1789,11 +1777,45 @@ export function useMode() { line.attributes.currentRoof = roof.lines[index].id line.attributes.planeSize = lineLength line.attributes.actualSize = lineLength + + let wallStroke, wallStrokeWidth + switch (line.attributes.type) { + case LINE_TYPE.WALLLINE.EAVES: + wallStroke = '#45CD7D' + wallStrokeWidth = 4 + break + case LINE_TYPE.WALLLINE.HIPANDGABLE: + wallStroke = '#45CD7D' + wallStrokeWidth = 4 + break + case LINE_TYPE.WALLLINE.GABLE: + wallStroke = '#3FBAE6' + wallStrokeWidth = 4 + break + case LINE_TYPE.WALLLINE.JERKINHEAD: + wallStroke = '#3FBAE6' + wallStrokeWidth = 4 + break + case LINE_TYPE.WALLLINE.SHED: + wallStroke = '#000000' + wallStrokeWidth = 4 + break + case LINE_TYPE.WALLLINE.WALL: + wallStroke = '#000000' + wallStrokeWidth = 4 + break + } + const wallLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { + attributes: { wallId: wall.id }, + stroke: wallStroke, + strokeWidth: wallStrokeWidth, + selectable: false, + }) + canvas.add(wallLine) }) setRoof(roof) setWall(wall) - return roof } diff --git a/src/hooks/usePlan.js b/src/hooks/usePlan.js index ab67452e..7ed3fa6e 100644 --- a/src/hooks/usePlan.js +++ b/src/hooks/usePlan.js @@ -57,6 +57,7 @@ export function usePlan() { */ const currentCanvasData = (mode = '') => { removeMouseLines() + canvas.discardActiveObject() if (mode === 'save') { const groups = canvas.getObjects().filter((obj) => obj.type === 'group') @@ -141,7 +142,9 @@ export function usePlan() { */ const checkUnsavedCanvasPlan = async (userId) => { swalFire({ - text: `저장 안된 ${currentCanvasPlan.name} PLAN을 저장하시겠습니까? `, + text: + (!initCanvasPlans.some((initCanvasPlans) => initCanvasPlans.id === plan.id) ? 'New ' : '') + + `Plan ${plan.ordering}의 변경 사항을 저장하시겠습니까?`, type: 'confirm', confirmFn: async () => { initCanvasPlans.some((plan) => plan.id === currentCanvasPlan.id) @@ -181,20 +184,20 @@ export function usePlan() { */ const getCanvasByObjectNo = async (userId, objectNo) => { return get({ url: `/api/canvas-management/canvas-statuses/by-object/${objectNo}/${userId}` }).then((res) => - res.map((item) => ({ + res.map((item, index) => ({ id: item.id, - name: item.objectNo + '-' + item.id, // tab button에 표출될 이름 (임시) userId: item.userId, canvasStatus: dbToCanvasFormat(item.canvasStatus), isCurrent: false, bgImageName: item.bgImageName, mapPositionAddress: item.mapPositionAddress, + ordering: index + 1, })), ) } /** - * canvas 데이터를 추가 + * 신규 canvas 데이터를 저장 */ const postCanvasStatus = async (userId, canvasStatus) => { const planData = { @@ -212,7 +215,6 @@ export function usePlan() { ? { ...plan, id: res.data, - name: currentCanvasPlan.objectNo + '-' + res.data, canvasStatus: canvasStatus, } : plan, @@ -302,7 +304,10 @@ export function usePlan() { const handleAddPlan = (userId, objectNo) => { JSON.parse(currentCanvasData()).objects.length > 0 ? swalFire({ - text: `${currentCanvasPlan.name} ` + getMessage('plan.message.confirm.copy'), + text: + (!initCanvasPlans.some((initCanvasPlans) => initCanvasPlans.id === currentCanvasPlan.id) ? 'New ' : '') + + `Plan ${currentCanvasPlan.ordering} ` + + getMessage('plan.message.confirm.copy'), type: 'confirm', confirmFn: () => { addPlan(userId, objectNo, currentCanvasData()) @@ -317,10 +322,10 @@ export function usePlan() { const id = uuidv4() const newPlan = { id: id, - name: `Plan ${planNum + 1}`, objectNo: objectNo, userId: userId, canvasStatus: canvasStatus, + ordering: planNum + 1, } setPlans([...plans, newPlan]) handleCurrentPlan(userId, id) @@ -363,16 +368,16 @@ export function usePlan() { /** * plan 조회 */ - const loadCanvasPlanData = (userId, objectNo) => { + const loadCanvasPlanData = (userId, objectNo, pid) => { getCanvasByObjectNo(userId, objectNo).then((res) => { // console.log('canvas 목록 ', res) if (res.length > 0) { setInitCanvasPlans(res) setPlans(res) - updateCurrentPlan(res.at(-1).id) // last 데이터에 포커싱 + updateCurrentPlan(Number(pid)) setPlanNum(res.length) } else { - addPlan(userId, objectNo) + addPlan(userId, objectNo, '') } }) } @@ -380,6 +385,7 @@ export function usePlan() { return { canvas, plans, + initCanvasPlans, selectedPlan, currentCanvasPlan, modifiedPlans, diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 9b3a8e98..a844dd6b 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -1,7 +1,7 @@ import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, fontFamilyState, fontSizeState, pitchTextSelector } from '@/store/canvasAtom' import { useRecoilValue } from 'recoil' import { fabric } from 'fabric' -import { getDegreeByChon, getDirectionByPoint, isPointOnLine } from '@/util/canvas-util' +import { findAndRemoveClosestPoint, getDegreeByChon, getDegreeInOrientation, getDirectionByPoint, isPointOnLine } from '@/util/canvas-util' import { QPolygon } from '@/components/fabric/QPolygon' import { isSamePoint, removeDuplicatePolygons } from '@/util/qpolygon-utils' import { flowDisplaySelector } from '@/store/settingAtom' @@ -86,8 +86,8 @@ export const usePolygon = () => { parentDirection: line.direction, parentDegree: degree, parentId: polygon.id, - planeSize, - actualSize, + planeSize: planeSize ?? length, + actualSize: actualSize ?? length, editable: false, selectable: true, lockRotation: true, @@ -181,7 +181,7 @@ export const usePolygon = () => { polygon.canvas .getObjects() - .filter((obj) => obj.name === 'flowText' && obj.parent === polygon.arrow) + .filter((obj) => obj.name === 'flowText' && obj.parentId === polygon.arrow?.id) .forEach((obj) => polygon.canvas.remove(obj)) let arrow = null @@ -265,6 +265,8 @@ export const usePolygon = () => { direction: direction, parent: polygon, stickeyPoint: stickeyPoint, + surfaceCompass: polygon.surfaceCompass, + moduleCompass: polygon.moduleCompass, visible: isFlowDisplay, pitch: polygon.pitch, parentId: polygon.id, @@ -274,7 +276,177 @@ export const usePolygon = () => { polygon.arrow = arrow polygon.canvas.add(arrow) polygon.canvas.renderAll() - drawDirectionStringToArrow(polygon.canvas, 0) + drawDirectionStringToArrow2(polygon) + // drawDirectionStringToArrow() + } + + //arrow의 compass 값으로 방향 글자 설정 필요 + const drawDirectionStringToArrow2 = (polygon) => { + const { direction, surfaceCompass, moduleCompass, arrow } = polygon + if (moduleCompass === null || moduleCompass === undefined) { + const textObj = new fabric.Text(`${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText}`, { + fontFamily: flowFontOptions.fontFamily.value, + fontWeight: flowFontOptions.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', + fontStyle: flowFontOptions.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', + fontSize: flowFontOptions.fontSize.value, + fill: flowFontOptions.fontColor.value, + originX: 'center', + originY: 'center', + pitch: arrow.pitch, + name: 'flowText', + selectable: false, + left: arrow.stickeyPoint.x, + top: arrow.stickeyPoint.y, + parent: arrow, + parentId: arrow.id, + visible: isFlowDisplay, + }) + + polygon.canvas.add(textObj) + return + } + + let text = '' + + const compassType = (375 - getDegreeInOrientation(moduleCompass)) / 15 + + if ([1, 25].includes(compassType)) { + direction === 'north' ? (text = '北') : direction === 'south' ? (text = '南') : direction === 'west' ? (text = '西') : (text = '東') + } else if ([2, 3].includes(compassType)) { + direction === 'north' + ? (text = '北北東') + : direction === 'south' + ? (text = '南南西') + : direction === 'west' + ? (text = '西北西') + : (text = '東南東') + } else if ([4].includes(compassType)) { + direction === 'north' ? (text = '北東') : direction === 'south' ? (text = '南西') : direction === 'west' ? (text = '北西') : (text = '南東') + } else if ([5, 6].includes(compassType)) { + direction === 'north' + ? (text = '東北東') + : direction === 'south' + ? (text = '西南西') + : direction === 'west' + ? (text = '北北西') + : (text = '南南東') + } else if ([7].includes(compassType)) { + direction === 'north' ? (text = '東') : direction === 'south' ? (text = '西') : direction === 'west' ? (text = '北') : (text = '南') + } else if ([8, 9].includes(compassType)) { + direction === 'north' + ? (text = '東南東') + : direction === 'south' + ? (text = '西北西') + : direction === 'west' + ? (text = '北北東') + : (text = '南南西') + } else if ([10].includes(compassType)) { + direction === 'north' ? (text = '南東') : direction === 'south' ? (text = '北西') : direction === 'west' ? (text = '南西') : (text = '北東') + } else if ([11, 12].includes(compassType)) { + direction === 'north' + ? (text = '南南東') + : direction === 'south' + ? (text = '北北西') + : direction === 'west' + ? (text = '東北東') + : (text = '西南西') + } else if ([13].includes(compassType)) { + direction === 'north' ? (text = '南') : direction === 'south' ? (text = '北') : direction === 'west' ? (text = '東') : (text = '西') + } else if ([14, 15].includes(compassType)) { + direction === 'north' + ? (text = '南南西') + : direction === 'south' + ? (text = '北北東') + : direction === 'west' + ? (text = '東南東') + : (text = '西北西') + } else if ([16].includes(compassType)) { + direction === 'north' ? (text = '南西') : direction === 'south' ? (text = '北東') : direction === 'west' ? (text = '南東') : (text = '北西') + } else if ([17, 18].includes(compassType)) { + direction === 'north' + ? (text = '西南西') + : direction === 'south' + ? (text = '東北東') + : direction === 'west' + ? (text = '南南東') + : (text = '北北西') + } else if ([19].includes(compassType)) { + direction === 'north' ? (text = '西') : direction === 'south' ? (text = '東') : direction === 'west' ? (text = '南') : (text = '北') + } else if ([20, 21].includes(compassType)) { + direction === 'north' + ? (text = '西北西') + : direction === 'south' + ? (text = '東南東') + : direction === 'west' + ? (text = '南南西') + : (text = '北北東') + } else if ([22].includes(compassType)) { + direction === 'north' ? (text = '北西') : direction === 'south' ? (text = '南東') : direction === 'west' ? (text = '南西') : (text = '北東') + } else if ([23, 24].includes(compassType)) { + direction === 'north' + ? (text = '北北西') + : direction === 'south' + ? (text = '南南東') + : direction === 'west' + ? (text = '西南西') + : (text = '東北東') + } + + // 東,西,南,北 + if ([360].includes(surfaceCompass)) { + text = '南' + } else if ([345, 330].includes(surfaceCompass)) { + text = '南南東' + } else if ([315].includes(surfaceCompass)) { + text = '南東' + } else if ([300, 285].includes(surfaceCompass)) { + text = '東南東' + } else if ([270].includes(surfaceCompass)) { + text = '東' + } else if ([255, 240].includes(surfaceCompass)) { + text = '東北東' + } else if ([225].includes(surfaceCompass)) { + text = '北東' + } else if ([210, 195].includes(surfaceCompass)) { + text = '北北東' + } else if ([180].includes(surfaceCompass)) { + text = '北' + } else if ([165, 150].includes(surfaceCompass)) { + text = '北北西' + } else if ([135].includes(surfaceCompass)) { + text = '北西' + } else if ([120, 105].includes(surfaceCompass)) { + text = '西北西' + } else if ([90].includes(surfaceCompass)) { + text = '西' + } else if ([75, 60].includes(surfaceCompass)) { + text = '西南西' + } else if ([45].includes(surfaceCompass)) { + text = '西南' + } else if ([30, 15].includes(surfaceCompass)) { + text = '西西南' + } + + const textObj = new fabric.Text(`${text} (${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText})`, { + fontFamily: flowFontOptions.fontFamily.value, + fontWeight: flowFontOptions.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', + fontStyle: flowFontOptions.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', + fontSize: flowFontOptions.fontSize.value, + fill: flowFontOptions.fontColor.value, + pitch: arrow.pitch, + originX: 'center', + originY: 'center', + name: 'flowText', + originText: text, + selectable: false, + left: arrow.stickeyPoint.x, + top: arrow.stickeyPoint.y, + parent: arrow, + parentId: arrow.id, + visible: isFlowDisplay, + }) + + polygon.canvas.add(textObj) } /** @@ -473,10 +645,11 @@ export const usePolygon = () => { const textStr = `${txt} (${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText})` const text = new fabric.Text(`${textStr}`, { + fontFamily: flowFontOptions.fontFamily.value, + fontWeight: flowFontOptions.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', + fontStyle: flowFontOptions.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', fontSize: flowFontOptions.fontSize.value, fill: flowFontOptions.fontColor.value, - fontFamily: flowFontOptions.fontFamily.value, - fontWeight: flowFontOptions.fontWeight.value, pitch: arrow.pitch, originX: 'center', originY: 'center', @@ -496,62 +669,159 @@ export const usePolygon = () => { const splitPolygonWithLines = (polygon) => { polygon.set({ visible: false }) let innerLines = [...polygon.innerLines] + + canvas.renderAll() let polygonLines = [...polygon.lines] const roofs = [] - let delIndexs = [] - let newLines = [] + //polygonLines를 순회하며 innerLines와 교차하는 점을 line의 속성에 배열로 저장한다. + polygonLines.forEach((line) => { + let startPoint // 시작점 + let endPoint // 끝점 + if (line.x1 < line.x2) { + startPoint = { x: line.x1, y: line.y1 } + endPoint = { x: line.x2, y: line.y2 } + } else if (line.x1 > line.x2) { + startPoint = { x: line.x2, y: line.y2 } + endPoint = { x: line.x1, y: line.y1 } + } else { + if (line.y1 < line.y2) { + startPoint = { x: line.x1, y: line.y1 } + endPoint = { x: line.x2, y: line.y2 } + } else { + startPoint = { x: line.x2, y: line.y2 } + endPoint = { x: line.x1, y: line.y1 } + } + } - polygonLines.forEach((line, index) => { - line.tempIndex = index + line.startPoint = startPoint + line.endPoint = endPoint + }) + innerLines.forEach((line) => { + let startPoint // 시작점 + let endPoint // 끝점 + if (line.x1 < line.x2) { + startPoint = { x: line.x1, y: line.y1 } + endPoint = { x: line.x2, y: line.y2 } + } else if (line.x1 > line.x2) { + startPoint = { x: line.x2, y: line.y2 } + endPoint = { x: line.x1, y: line.y1 } + } else { + if (line.y1 < line.y2) { + startPoint = { x: line.x1, y: line.y1 } + endPoint = { x: line.x2, y: line.y2 } + } else { + startPoint = { x: line.x2, y: line.y2 } + endPoint = { x: line.x1, y: line.y1 } + } + } + + line.startPoint = startPoint + line.endPoint = endPoint + }) + + polygonLines.forEach((line) => { + line.set({ strokeWidth: 10 }) + canvas.add(line) + }) + canvas.renderAll() + + polygonLines.forEach((line) => { + const intersections = [] innerLines.forEach((innerLine) => { - let newLine1, newLine2 if (isPointOnLine(line, innerLine.startPoint)) { - // 해당 line을 startPoint로 나눈 line2개를 canvas에 추가 하고 기존 line을 제거한다. - newLine1 = new QLine([line.x1, line.y1, innerLine.startPoint.x, innerLine.startPoint.y], { - fontSize: polygon.fontSize, - stroke: 'black', - strokeWidth: 3, - }) - - newLine2 = new QLine([innerLine.startPoint.x, innerLine.startPoint.y, line.x2, line.y2], { - fontSize: polygon.fontSize, - stroke: 'black', - strokeWidth: 3, - }) - delIndexs.push(polygonLines.indexOf(line)) - canvas.remove(polygonLines[polygonLines.indexOf(line)]) - if (newLine1.length / 10 > 10) { - newLines.push(newLine1) - } - if (newLine2.length / 10 > 10) { - newLines.push(newLine2) + if (isSamePoint(line.startPoint, innerLine.startPoint) || isSamePoint(line.endPoint, innerLine.startPoint)) { + return } + intersections.push(innerLine.startPoint) } if (isPointOnLine(line, innerLine.endPoint)) { - newLine1 = new QLine([line.x1, line.y1, innerLine.endPoint.x, innerLine.endPoint.y], { - fontSize: polygon.fontSize, - stroke: 'black', - strokeWidth: 3, - }) - - newLine2 = new QLine([innerLine.endPoint.x, innerLine.endPoint.y, line.x2, line.y2], { - fontSize: polygon.fontSize, - stroke: 'black', - strokeWidth: 3, - }) - delIndexs.push(polygonLines.indexOf(line)) - canvas.remove(polygonLines[polygonLines.indexOf(line)]) - if (newLine1.length / 10 > 10) { - newLines.push(newLine1) - } - if (newLine2.length / 10 > 10) { - newLines.push(newLine2) + if (isSamePoint(line.startPoint, innerLine.endPoint) || isSamePoint(line.endPoint, innerLine.endPoint)) { + return } + intersections.push(innerLine.endPoint) } }) + line.set({ intersections }) }) - polygonLines = polygonLines.filter((line) => !delIndexs.includes(line.tempIndex)) + + const divideLines = polygonLines.filter((line) => line.intersections.length > 0) + let newLines = [] + + divideLines.forEach((line) => { + const { intersections, startPoint, endPoint } = line + + if (intersections.length === 1) { + // 한 점만 교차하는 경우 + const newLinePoint1 = [line.x1, line.y1, intersections[0].x, intersections[0].y] + const newLinePoint2 = [intersections[0].x, intersections[0].y, line.x2, line.y2] + const newLine1 = new QLine(newLinePoint1, { + stroke: 'blue', + strokeWidth: 3, + fontSize: polygon.fontSize, + attributes: line.attributes, + }) + const newLine2 = new QLine(newLinePoint2, { + stroke: 'blue', + strokeWidth: 3, + fontSize: polygon.fontSize, + attributes: line.attributes, + }) + newLine1.attributes = { + ...line.attributes, + planeSize: Math.round(Math.sqrt(Math.pow(newLine1.x1 - newLine1.x2, 2) + Math.pow(newLine1.y1 - newLine1.y2, 2))) * 10, + actualSize: Math.round(Math.sqrt(Math.pow(newLine1.x1 - newLine1.x2, 2) + Math.pow(newLine1.y1 - newLine1.y2, 2))) * 10, + } + newLine2.attributes = { + ...line.attributes, + planeSize: Math.round(Math.sqrt(Math.pow(newLine2.x1 - newLine2.x2, 2) + Math.pow(newLine2.y1 - newLine2.y2, 2))) * 10, + actualSize: Math.round(Math.sqrt(Math.pow(newLine2.x1 - newLine2.x2, 2) + Math.pow(newLine2.y1 - newLine2.y2, 2))) * 10, + } + newLines.push(newLine1) + newLines.push(newLine2) + } else { + // 두 점 이상 교차하는 경우 + //1. intersections중에 startPoint와 가장 가까운 점을 찾는다. + //2. 가장 가까운 점을 기준으로 line을 나눈다. + + let currentPoint = startPoint + + while (intersections.length !== 0) { + const minDistancePoint = findAndRemoveClosestPoint(currentPoint, intersections) + const newLinePoint = [currentPoint.x, currentPoint.y, minDistancePoint.x, minDistancePoint.y] + const newLine = new QLine(newLinePoint, { + stroke: 'blue', + strokeWidth: 3, + fontSize: polygon.fontSize, + attributes: line.attributes, + }) + newLine.attributes = { + ...line.attributes, + planeSize: Math.round(Math.sqrt(Math.pow(newLine.x1 - newLine.x2, 2) + Math.pow(newLine.y1 - newLine.y2, 2))) * 10, + actualSize: Math.round(Math.sqrt(Math.pow(newLine.x1 - newLine.x2, 2) + Math.pow(newLine.y1 - newLine.y2, 2))) * 10, + } + newLines.push(newLine) + currentPoint = minDistancePoint + } + + const newLinePoint = [currentPoint.x, currentPoint.y, endPoint.x, endPoint.y] + const newLine = new QLine(newLinePoint, { + stroke: 'blue', + strokeWidth: 3, + fontSize: polygon.fontSize, + attributes: line.attributes, + }) + newLine.attributes = { + ...line.attributes, + planeSize: Math.round(Math.sqrt(Math.pow(newLine.x1 - newLine.x2, 2) + Math.pow(newLine.y1 - newLine.y2, 2))) * 10, + actualSize: Math.round(Math.sqrt(Math.pow(newLine.x1 - newLine.x2, 2) + Math.pow(newLine.y1 - newLine.y2, 2))) * 10, + } + newLines.push(newLine) + } + }) + + //polygonLines에서 divideLines를 제거하고 newLines를 추가한다. + polygonLines = polygonLines.filter((line) => !divideLines.includes(line)) polygonLines = [...polygonLines, ...newLines] const allLines = [...polygonLines, ...innerLines] @@ -583,6 +853,9 @@ export const usePolygon = () => { }) polygonLines.forEach((line) => { + line.set({ strokeWidth: 5, stroke: 'green' }) + canvas.add(line) + canvas.renderAll() const startPoint = line.startPoint // 시작점 let arrivalPoint = line.endPoint // 도착점 @@ -646,17 +919,18 @@ export const usePolygon = () => { roofPoints.push(currentPoint) cnt++ if (cnt > 100) { - throw new Error('무한루프') + break } } roofs.push(roofPoints) + canvas.remove(line) + canvas.renderAll() }) - const newRoofs = removeDuplicatePolygons(roofs) + const newRoofs = removeDuplicatePolygons(roofs.filter((roof) => roof.length < 100)) newRoofs.forEach((roofPoint, index) => { let defense, pitch - const polygonLines = [...polygon.lines] let representLines = [] let representLine @@ -728,7 +1002,7 @@ export const usePolygon = () => { //allLines중 생성된 roof와 관련있는 line을 찾는다. - roof.lines = [...polygon.lines, ...polygon.innerLines].filter((line) => { + roof.lines = [...polygonLines, ...polygon.innerLines].filter((line) => { let startFlag = false let endFlag = false const startPoint = line.startPoint @@ -752,6 +1026,47 @@ export const usePolygon = () => { }) } + const splitPolygonWithSeparate = (separates) => { + separates.forEach((separate) => { + const points = separate.lines.map((line) => { + return { x: line.x1, y: line.y1 } + }) + let defense = '' + switch (separate.attributes.direction) { + case 'top': + defense = 'east' + break + case 'right': + defense = 'south' + break + case 'bottom': + defense = 'west' + break + case 'left': + defense = 'north' + break + } + + const roof = new QPolygon(points, { + fontSize: separate.fontSize, + stroke: 'black', + fill: 'transparent', + strokeWidth: 3, + name: POLYGON_TYPE.ROOF, + originX: 'center', + originY: 'center', + selectable: true, + defense: defense, + pitch: separate.attributes.pitch, + direction: defense, + }) + + canvas.add(roof) + }) + + canvas.renderAll() + } + return { addPolygon, addPolygonByLines, @@ -759,5 +1074,6 @@ export const usePolygon = () => { drawDirectionArrow, addLengthText, splitPolygonWithLines, + splitPolygonWithSeparate, } } diff --git a/src/lib/cadAction.js b/src/lib/cadAction.js index 05bc878a..b2feafff 100644 --- a/src/lib/cadAction.js +++ b/src/lib/cadAction.js @@ -1,5 +1,10 @@ 'use server' +/** + * Deprecated + * 개발후 삭제 예정 + */ + import fs from 'fs/promises' const imageSavePath = 'public/cadImages' diff --git a/src/lib/canvas.js b/src/lib/canvas.js index f8855e33..58a6eb50 100644 --- a/src/lib/canvas.js +++ b/src/lib/canvas.js @@ -1,5 +1,10 @@ 'use server' +/** + * Deprecated + * 개발후 삭제 예정 + */ + // import { PrismaClient } from '@prisma/client' import fs from 'fs/promises' diff --git a/src/lib/file.js b/src/lib/file.js deleted file mode 100644 index 418fbe96..00000000 --- a/src/lib/file.js +++ /dev/null @@ -1,22 +0,0 @@ -'use server' -import path from 'path' -import multer from 'multer' - -export const upload = (files) => { - console.log(files) - const storage = multer.diskStorage({ - destination: (req, file, callback) => { - const extension = path.extname(file.originalname) - const basename = path.basename(file.originalname, extension) - callback(null, `/public/upload/${basename}-${Date.now()}${extension}`) - }, - filename: (req, file, callback) => { - callback(null, `${file.fieldname}-${Date.now()}${path.extname(file.originalname)}`) - } - }) - const test = multer({ - storage: storage - }).array(files.name, 5) - - console.log(test) -} diff --git a/src/lib/fileAction.js b/src/lib/fileAction.js new file mode 100644 index 00000000..1cea19be --- /dev/null +++ b/src/lib/fileAction.js @@ -0,0 +1,64 @@ +'use server' + +import fs from 'fs/promises' + +const CAD_FILE_PATH = 'public/cad-images' +const IMAGE_FILE_PATH = 'public/plan-bg-images' + +/** + * 파일 변환 & 저장 + * @param {*} fileName + * @param {*} data + * @returns + */ +const convertDwgToPng = async (fileName, data) => { + console.log('fileName', fileName) + try { + await fs.readdir(CAD_FILE_PATH) + } catch { + await fs.mkdir(CAD_FILE_PATH) + } + + return await fs.writeFile(`${CAD_FILE_PATH}/${fileName}`, data, 'base64') +} + +/** + * 이미지 저장 + * base64 형식으로 저장 + * @param {*} title + * @param {*} data + * @returns + */ +const writeImageBase64 = async (title, data) => { + // 해당 경로에 Directory 가 없다면 생성 + try { + await fs.readdir(IMAGE_FILE_PATH) + } catch { + await fs.mkdir(IMAGE_FILE_PATH) + } + + return fs.writeFile(`${IMAGE_FILE_PATH}/${title}.png`, data, 'base64') +} + +/** + * 이미지 저장 + * Buffer 형식으로 저장 + * @param {*} title + * @param {*} data + * @returns + */ +const writeImageBuffer = async (file) => { + // 해당 경로에 Directory 가 없다면 생성 + try { + await fs.readdir(IMAGE_FILE_PATH) + } catch { + await fs.mkdir(IMAGE_FILE_PATH) + } + + const arrayBuffer = await fileURLToPath.arrayBuffer() + const buffer = new Uint8Array(arrayBuffer) + + return fs.writeFile(`${IMAGE_FILE_PATH}/${file.fileName}`, buffer) +} + +export { convertDwgToPng, writeImageBase64, writeImageBuffer } diff --git a/src/lib/user.js b/src/lib/user.js deleted file mode 100644 index d79e958a..00000000 --- a/src/lib/user.js +++ /dev/null @@ -1,41 +0,0 @@ -'use server' - -import { getSession } from './authActions' - -const { PrismaClient } = require('@prisma/client') - -const prisma = new PrismaClient() - -export async function getUserByIdAndPassword({ userId, password }) { - return prisma.m_USER.findFirst({ - where: { - USER_ID: userId, - PASSWORD: password, - }, - }) -} - -export async function getUser(userId) { - return prisma.m_USER.findUnique({ - where: { - user_id: userId, - }, - }) -} - -export async function getUsers() { - return prisma.m_USER.findMany({ - where: { - // USER_ID: 'daiwajoho01', - USER_ID: { in: ['daiwajoho01', 'daiwajoho', 'daiwabutsuryu'] }, - }, - }) -} - -export async function checkSession() { - const session = await getSession() - - return { - session, - } -} diff --git a/src/locales/ja.json b/src/locales/ja.json index ec6a7b69..2f8be755 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -4,6 +4,7 @@ "header.menus.home": "ホームへv", "header.menus.management": "物品及び図面管理", "header.menus.management.newStuff": "新規 物件 登録", + "header.menus.management.detail": "物件詳細", "header.menus.management.stuffList": "物件の状況", "header.menus.community": "コミュニティ", "header.menus.community.notice": "お知らせ", @@ -85,12 +86,14 @@ "modal.module.basic.setting.orientation.setting.angle.passivity": "角度を直接入力", "modal.module.basic.setting.module.roof.material": "屋根材", "modal.module.basic.setting.module.trestle.maker": "架台メーカー", + "modal.module.basic.setting.module.rafter.margin": "マンドンピッチ", "modal.module.basic.setting.module.construction.method": "工法", "modal.module.basic.setting.module.under.roof": "屋根の下", "modal.module.basic.setting.module.setting": "モジュールの選択", "modal.module.basic.setting.module.setting.info1": "※勾配の 範囲には制限があります。屋根傾斜が2.5値未満、10値を超える場合には施工が可能か 施工マニュアルを確認してください。", "modal.module.basic.setting.module.setting.info2": "モジュール配置時は、施工マニュアルに記載されている<モジュール配置条件>を必ず確認してください", - "modal.module.basic.setting.module.cotton.classification": "綿調道区分", + "modal.module.basic.setting.module.stuff.info": "商品情報", + "modal.module.basic.setting.module.surface.type": "면조도구분(JA)", "modal.module.basic.setting.module.fitting.height": "設置高さ", "modal.module.basic.setting.module.standard.wind.speed": "基準風速", "modal.module.basic.setting.module.standard.snowfall.amount": "基準積雪量", @@ -160,7 +163,7 @@ "plan.menu.estimate.docDown": "文書のダウンロード", "plan.menu.estimate.save": "保存", "plan.menu.estimate.reset": "初期化", - "plan.menu.estimate.copy": "コピー", + "plan.menu.estimate.copy": "見積書のコピー", "plan.menu.simulation": "発展シミュレーション", "plan.menu.simulation.excel": "Excel", "plan.menu.simulation.pdf": "PDF", @@ -284,7 +287,6 @@ "modal.panel.batch.statistic.total": "合計", "modal.flow.direction.setting": "流れ方向の設定", "modal.flow.direction.setting.info": "流れ方向を選択してください。", - "modal.image.size.setting": "画像のサイズ変更", "modal.actual.size.setting": "実測値設定", "modal.actual.size.setting.info": "※隅棟・谷・棟の実際の寸法を入力してください。", "modal.actual.size.setting.not.exist.auxiliary.line": "실측치 입력할 보조선을 선택해 주세요(JA)", @@ -616,9 +618,9 @@ "stuff.planReqPopup.title": "設計依頼のインポート", "stuff.temp.subTitle": "商品情報", "stuff.temp.subTitle2": "作図", - "stuff.detail.header.message1": "存在しないものです。", - "stuff.detail.header.message2": "商品番号がコピーされました。", - "stuff.detail.header.message3": "存在しないものです。", + "stuff.detail.header.notExistObjectNo": "存在しないものです。", + "stuff.detail.header.successCopy": "商品番号がコピーされました。", + "stuff.detail.header.failCopy": "存在しないものです。", "stuff.detail.header.objectNo": "商品番号のコピーに失敗しました。", "stuff.detail.header.specificationConfirmDate": "仕様拡張日", "stuff.detail.header.lastEditDatetime": "更新日時", @@ -667,6 +669,7 @@ "stuff.detail.btn.save": "保存", "stuff.detail.btn.tempSave": "一時保存", "stuff.detail.save": "保存しました", + "stuff.detail.tempSave": "一時保存されました", "stuff.detail.noChgData": "変更内容はありません", "stuff.detail.save.valierror1": "垂直説説は0より大きい値を入力してください", "stuff.detail.save.valierror2": "設置高さ0より大きい値を入力してください", @@ -694,11 +697,12 @@ "stuff.planReqPopup.search.period": "期間検索", "stuff.planReqPopup.search.schDateGbnS": "提出日", "stuff.planReqPopup.search.schDateGbnR": "受付日", - "stuff.planReqPopup.error.message1": "設計依頼を選択してください。", + "stuff.planReqPopup.error.message1": "設計依頼を選択してください.", + "stuff.planReqPopup.error.message2": "販売店を選択してください.", "stuff.search.title": "物件状況", - "stuff.search.btn1": "新規 物件 登録", - "stuff.search.btn2": "照会", - "stuff.search.btn3": "初期化", + "stuff.search.btn.register": "新規 物件 登録", + "stuff.search.btn.search": "照会", + "stuff.search.btn.reset": "初期化", "stuff.search.schObjectNo": "品番", "stuff.search.schSaleStoreName": "販売代理店名", "stuff.search.schAddress": "商品アドレス", @@ -727,12 +731,12 @@ "stuff.detail.planGridHeader.moduleModel": "モジュール", "stuff.detail.planGridHeader.capacity": "システム容量", "stuff.detail.planGridHeader.roofMaterialIdMulti": "屋根材", - "stuff.detail.planGridHeader.constructSpecification": "施工方法", + "stuff.detail.planGridHeader.constructSpecificationMulti": "施工方法", "stuff.detail.planGridHeader.supportMethodIdMulti": "架台", "stuff.detail.planGridHeader.pcTypeNo": "パワーコンディショナー", "stuff.detail.planGridHeader.management": "管理", "stuff.detail.planGrid.btn1": "見積書の照会", - "stuff.detail.planGrid.btn2": "Excel", + "stuff.detail.planGrid.docDownload": "文書のダウンロード", "stuff.grid.noData": "照会されたデータがありません", "length": "長さ", "height": "高さ", @@ -822,6 +826,8 @@ "estimate.detail.objectName": "案件名", "estimate.detail.objectRemarks": "メモ", "estimate.detail.estimateType": "注文分類", + "estimate.detail.estimateType.yjss": "住宅PKG", + "estimate.detail.estimateType.yjod": "積上げ( YJOD )", "estimate.detail.roofCns": "屋根材・仕様施工", "estimate.detail.remarks": "備考", "estimate.detail.fileFlg": "後日資料提出", @@ -830,19 +836,18 @@ "estimate.detail.header.fileList2": "添付ファイル一覧", "estimate.detail.header.specialEstimate": "見積もりの具体的な", "estimate.detail.header.specialEstimateProductInfo": "製品情報", - "estimate.detail.sepcialEstimateProductInfo.totPcs": "数量 (PCS)", - "estimate.detail.sepcialEstimateProductInfo.vol": "容量 (Kw)", - "estimate.detail.sepcialEstimateProductInfo.netAmt": "供給価格", - "estimate.detail.sepcialEstimateProductInfo.vat": "付加価値税 (10%)", + "estimate.detail.sepcialEstimateProductInfo.totAmount": "数量 (PCS)", + "estimate.detail.sepcialEstimateProductInfo.totVolKw": "容量 (Kw)", + "estimate.detail.sepcialEstimateProductInfo.supplyPrice": "供給価格", + "estimate.detail.sepcialEstimateProductInfo.vatPrice": "付加価値税 (10%)", "estimate.detail.sepcialEstimateProductInfo.totPrice": "総額", "estimate.detail.sepcialEstimateProductInfo.pkgUnitPrice": "住宅PKG単価 (W)", "estimate.detail.sepcialEstimateProductInfo.pkgWeight": "PKG容量 (Kw)", "estimate.detail.sepcialEstimateProductInfo.pkgPrice": "PKG金額", - "estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(モジュール容量 × 数量)÷1000", - "estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG単価 (W)×PKG容量(W)", "estimate.detail.header.showPrice": "価格表示", "estimate.detail.header.unitPrice": "定価", "estimate.detail.showPrice.pricingBtn": "Pricing", + "estimate.detail.showPrice.pricingBtn.noItemId": "Pricingが欠落しているアイテムがあります。 Pricingを進めてください.", "estimate.detail.showPrice.description1": "製品価格 OPEN", "estimate.detail.showPrice.description2": "追加, 変更資材", "estimate.detail.showPrice.description3": "添付必須", @@ -859,25 +864,41 @@ "estimate.detail.docPopup.title": "ドキュメントダウンロードオプションの設定", "estimate.detail.docPopup.explane": "ダウンロードする文書のオプションを選択したら、 [文書のダウンロード]ボタンをクリックします.", "estimate.detail.docPopup.schUnitPriceFlg": "ダウンロードファイル", - "estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg0": "見積もり Excel", - "estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg1": "定価用 Excel", - "estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg2": "見積もり PDF", - "estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg3": "定価用 PDF", + "estimate.detail.docPopup.schUnitPriceFlg.excelFlg0": "見積もり Excel", + "estimate.detail.docPopup.schUnitPriceFlg.excelFlg1": "定価用 Excel", + "estimate.detail.docPopup.schUnitPriceFlg.pdfFlg0": "見積もり PDF", + "estimate.detail.docPopup.schUnitPriceFlg.pdfFlg1": "定価用 PDF", "estimate.detail.docPopup.schDisplayFlg": "見積提出先表示名", "estimate.detail.docPopup.schDisplayFlg.schDisplayFlg0": "販売店名", "estimate.detail.docPopup.schDisplayFlg.schDisplayFlg1": "案件名", "estimate.detail.docPopup.schWeightFlg": "架台重量表を含む", - "estimate.detail.docPopup.schWeightFlg.schWeightFlg0": "含む", - "estimate.detail.docPopup.schWeightFlg.schWeightFlg1": "含まない", + "estimate.detail.docPopup.schWeightFlg.schWeightFlg1": "含む", + "estimate.detail.docPopup.schWeightFlg.schWeightFlg0": "含まない", "estimate.detail.docPopup.schDrawingFlg": "図面/シミュレーションファイルを含む", - "estimate.detail.docPopup.schDrawingFlg.schDrawingFlg0": "含む", - "estimate.detail.docPopup.schDrawingFlg.schDrawingFlg1": "含まない", + "estimate.detail.docPopup.schDrawingFlg.schDrawingFlg1": "含む", + "estimate.detail.docPopup.schDrawingFlg.schDrawingFlg0": "含まない", "estimate.detail.docPopup.close": "閉じる", "estimate.detail.docPopup.docDownload": "文書のダウンロード", + "estimate.detail.estimateCopyPopup.title": "見積もり", + "estimate.detail.estimateCopyPopup.explane": "見積書をコピーする販売店を設定します。見積もりは定価にコピーされます.", + "estimate.detail.estimateCopyPopup.label.saleStoreId": "一次販売店名 / ID", + "estimate.detail.estimateCopyPopup.label.otherSaleStoreId": "二次販売店名 / ID", + "estimate.detail.estimateCopyPopup.label.receiveUser": "担当者", + "estimate.detail.estimateCopyPopup.close": "閉じる", + "estimate.detail.estimateCopyPopup.copyBtn": "見積もり", + "estimate.detail.estimateCopyPopup.copy.alertMessage": "見積書がコピーされました. コピーした商品情報に移動します.", "estimate.detail.productFeaturesPopup.title": "製品特異事項", "estimate.detail.productFeaturesPopup.close": "閉じる", + "estimate.detail.productFeaturesPopup.requiredStoreId": "一次販売店は必須です.", + "estimate.detail.productFeaturesPopup.requiredReceiveUser": "担当者は必須です.", "estimate.detail.save.alertMsg": "保存されている見積書で製品を変更した場合、図面や回路には反映されません.", - "estimate.detail.save.requiredMsg": "ファイル添付が必須のアイテムがあります。ファイルを添付するか、後日添付をチェックしてください.", + "estimate.detail.save.requiredFileUpload": "ファイル添付が必須のアイテムがあります。ファイルを添付するか、後日添付をチェックしてください.", + "estimate.detail.save.requiredItem": "製品は1つ以上登録する必要があります.", + "estimate.detail.save.requiredCharger": "担当者は必須です.", + "estimate.detail.save.requiredObjectName": "案件名は必須です.", + "estimate.detail.save.requiredEstimateDate": "見積日は必須です.", + "estimate.detail.save.requiredAmount": "数量は0より大きい値を入力してください.", + "estimate.detail.save.requiredSalePrice": "単価は0より大きい値を入力してください.", "estimate.detail.reset.confirmMsg": "保存した見積書情報が初期化され、図面情報が反映されます。本当に初期化しますか?", "simulator.title.sub1": "物件番号", "simulator.title.sub2": "作成日", diff --git a/src/locales/ko.json b/src/locales/ko.json index 16d634d3..62c354a1 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -4,6 +4,7 @@ "header.menus.home": "Home", "header.menus.management": "물건 및 도면 관리", "header.menus.management.newStuff": "신규 물건 등록", + "header.menus.management.detail": "물건 상세", "header.menus.management.stuffList": "물건 현황", "header.menus.community": "커뮤니티", "header.menus.community.notice": "공지", @@ -88,12 +89,14 @@ "modal.module.basic.setting.orientation.setting.angle.passivity": "각도를 직접 입력", "modal.module.basic.setting.module.roof.material": "지붕재", "modal.module.basic.setting.module.trestle.maker": "가대메이거", + "modal.module.basic.setting.module.rafter.margin": "서까래 간격", "modal.module.basic.setting.module.construction.method": "공법", "modal.module.basic.setting.module.under.roof": "지붕밑바탕", - "modal.module.basic.setting.module.setting": "모듈 설정", + "modal.module.basic.setting.module.setting": "모듈 선택", "modal.module.basic.setting.module.setting.info1": "※ 구배의 범위에는 제한이 있습니다. 지붕경사가 2.5치 미만, 10치를 초과하는 경우에는 시공이 가능한지 시공 매뉴얼을 확인해주십시오.", "modal.module.basic.setting.module.setting.info2": "※ 모듈 배치 시에는 시공 매뉴얼에 기재된 <모듈 배치 조건>을 반드시 확인해주십시오.", - "modal.module.basic.setting.module.cotton.classification": "면조도구분", + "modal.module.basic.setting.module.stuff.info": "물건정보", + "modal.module.basic.setting.module.surface.type": "면조도구분", "modal.module.basic.setting.module.fitting.height": "설치높이", "modal.module.basic.setting.module.standard.wind.speed": "기준 풍속", "modal.module.basic.setting.module.standard.snowfall.amount": "기준 적설량", @@ -164,7 +167,7 @@ "plan.menu.estimate.docDown": "문서 다운로드", "plan.menu.estimate.save": "저장", "plan.menu.estimate.reset": "초기화", - "plan.menu.estimate.copy": "복사", + "plan.menu.estimate.copy": "견적서 복사", "plan.menu.simulation": "발전 시뮬레이션", "plan.menu.simulation.excel": "Excel", "plan.menu.simulation.pdf": "PDF", @@ -289,7 +292,6 @@ "modal.panel.batch.statistic.total": "합계", "modal.flow.direction.setting": "흐름 방향 설정", "modal.flow.direction.setting.info": "흐름방향을 선택하세요.", - "modal.image.size.setting": "이미지 크기 조절", "modal.actual.size.setting": "실측치 설정", "modal.actual.size.setting.info": "※隅棟・谷・棟의 실제 치수를 입력해주세요.", "modal.actual.size.setting.not.exist.auxiliary.line": "실측치 입력할 보조선을 선택해 주세요", @@ -626,9 +628,9 @@ "stuff.planReqPopup.title": "설계의뢰 불러오기", "stuff.temp.subTitle": "물건정보", "stuff.temp.subTitle2": "도면작성", - "stuff.detail.header.message1": "존재하지 않는 물건입니다.", - "stuff.detail.header.message2": "물건번호가 복사되었습니다.", - "stuff.detail.header.message3": "물건번호 복사에 실패했습니다.", + "stuff.detail.header.notExistObjectNo": "존재하지 않는 물건입니다.", + "stuff.detail.header.successCopy": "물건번호가 복사되었습니다.", + "stuff.detail.header.failCopy": "물건번호 복사에 실패했습니다.", "stuff.detail.header.objectNo": "물건번호", "stuff.detail.header.specificationConfirmDate": "사양확장일", "stuff.detail.header.lastEditDatetime": "갱신일시", @@ -677,6 +679,7 @@ "stuff.detail.btn.save": "저장", "stuff.detail.btn.tempSave": "임시저장", "stuff.detail.save": "저장되었습니다", + "stuff.detail.tempSave": "임시저장 되었습니다", "stuff.detail.noChgData": "변경된 내용이 없습니다", "stuff.detail.save.valierror1": "수직적설량은 0보다 큰 값을 입력하세요", "stuff.detail.save.valierror2": "설치높이는 0보다 큰 값을 입력하세요", @@ -705,10 +708,11 @@ "stuff.planReqPopup.search.schDateGbnS": "제출일", "stuff.planReqPopup.search.schDateGbnR": "접수일", "stuff.planReqPopup.error.message1": "설계의뢰를 선택해주세요.", + "stuff.planReqPopup.error.message2": "판매점을 선택해주세요.", "stuff.search.title": "물건현황", - "stuff.search.btn1": "신규 물건 등록", - "stuff.search.btn2": "조회", - "stuff.search.btn3": "초기화", + "stuff.search.btn.register": "신규 물건 등록", + "stuff.search.btn.search": "조회", + "stuff.search.btn.reset": "초기화", "stuff.search.schObjectNo": "물건번호", "stuff.search.schSaleStoreName": "판매대리점명", "stuff.search.schAddress": "물건주소", @@ -737,12 +741,12 @@ "stuff.detail.planGridHeader.moduleModel": "모듈", "stuff.detail.planGridHeader.capacity": "시스템용량", "stuff.detail.planGridHeader.roofMaterialIdMulti": "지붕재", - "stuff.detail.planGridHeader.constructSpecification": "시공방법", + "stuff.detail.planGridHeader.constructSpecificationMulti": "시공방법", "stuff.detail.planGridHeader.supportMethodIdMulti": "가대", "stuff.detail.planGridHeader.pcTypeNo": "파워컨디셔너", "stuff.detail.planGridHeader.management": "관리", "stuff.detail.planGrid.btn1": "견적서 조회", - "stuff.detail.planGrid.btn2": "Excel", + "stuff.detail.planGrid.docDownload": "문서 다운로드", "stuff.grid.noData": "조회된 데이터가 없습니다", "length": "길이", "height": "높이", @@ -832,6 +836,8 @@ "estimate.detail.objectName": "안건명", "estimate.detail.objectRemarks": "메모", "estimate.detail.estimateType": "주문분류", + "estimate.detail.estimateType.yjss": "住宅PKG", + "estimate.detail.estimateType.yjod": "積上げ( YJOD )", "estimate.detail.roofCns": "지붕재・사양시공", "estimate.detail.remarks": "비고", "estimate.detail.fileFlg": "후일자료제출", @@ -840,19 +846,18 @@ "estimate.detail.header.fileList2": "첨부파일 목록", "estimate.detail.header.specialEstimate": "견적특이사항", "estimate.detail.header.specialEstimateProductInfo": "제품정보", - "estimate.detail.sepcialEstimateProductInfo.totPcs": "수량 (PCS)", - "estimate.detail.sepcialEstimateProductInfo.vol": "용량 (Kw)", - "estimate.detail.sepcialEstimateProductInfo.netAmt": "공급가액", - "estimate.detail.sepcialEstimateProductInfo.vat": "부가세 (10%)", + "estimate.detail.sepcialEstimateProductInfo.totAmount": "수량 (PCS)", + "estimate.detail.sepcialEstimateProductInfo.totVolKw": "용량 (Kw)", + "estimate.detail.sepcialEstimateProductInfo.supplyPrice": "공급가액", + "estimate.detail.sepcialEstimateProductInfo.vatPrice": "부가세 (10%)", "estimate.detail.sepcialEstimateProductInfo.totPrice": "총액", "estimate.detail.sepcialEstimateProductInfo.pkgUnitPrice": "주택PKG단가 (W)", "estimate.detail.sepcialEstimateProductInfo.pkgWeight": "PKG 용량 (Kw)", "estimate.detail.sepcialEstimateProductInfo.pkgPrice": "PKG 금액", - "estimate.detail.sepcialEstimateProductInfo.calcFormula1": "(모듈수량 * 수량)÷100", - "estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG단가(W) * PKG용량(W)", "estimate.detail.header.showPrice": "가격표시", "estimate.detail.header.unitPrice": "정가", "estimate.detail.showPrice.pricingBtn": "Pricing", + "estimate.detail.showPrice.pricingBtn.noItemId": "Pricing이 누락된 아이템이 있습니다. Pricing을 진행해주세요.", "estimate.detail.showPrice.description1": "제품 가격 OPEN", "estimate.detail.showPrice.description2": "추가, 변경 자재", "estimate.detail.showPrice.description3": "첨부필수", @@ -869,25 +874,41 @@ "estimate.detail.docPopup.title": "문서다운로드 옵션설정", "estimate.detail.docPopup.explane": "다운로드할 문서 옵션을 선택한 후 문서 다운로드 버튼을 클릭합니다.", "estimate.detail.docPopup.schUnitPriceFlg": "다운로드 파일", - "estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg0": "견적가 Excel", - "estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg1": "정가용 Excel", - "estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg2": "견적가 PDF", - "estimate.detail.docPopup.schUnitPriceFlg.schUnitPriceFlg3": "정가용 PDF", + "estimate.detail.docPopup.schUnitPriceFlg.excelFlg0": "견적가 Excel", + "estimate.detail.docPopup.schUnitPriceFlg.excelFlg1": "정가용 Excel", + "estimate.detail.docPopup.schUnitPriceFlg.pdfFlg0": "견적가 PDF", + "estimate.detail.docPopup.schUnitPriceFlg.pdfFlg1": "정가용 PDF", "estimate.detail.docPopup.schDisplayFlg": "견적제출서 표시명", "estimate.detail.docPopup.schDisplayFlg.schDisplayFlg0": "판매점명", "estimate.detail.docPopup.schDisplayFlg.schDisplayFlg1": "안건명", "estimate.detail.docPopup.schWeightFlg": "가대 중량표 포함", - "estimate.detail.docPopup.schWeightFlg.schWeightFlg0": "포함", - "estimate.detail.docPopup.schWeightFlg.schWeightFlg1": "미포함", + "estimate.detail.docPopup.schWeightFlg.schWeightFlg1": "포함", + "estimate.detail.docPopup.schWeightFlg.schWeightFlg0": "미포함", "estimate.detail.docPopup.schDrawingFlg": "도면/시뮬레이션 파일 포함", - "estimate.detail.docPopup.schDrawingFlg.schDrawingFlg0": "포함", - "estimate.detail.docPopup.schDrawingFlg.schDrawingFlg1": "미포함", + "estimate.detail.docPopup.schDrawingFlg.schDrawingFlg1": "포함", + "estimate.detail.docPopup.schDrawingFlg.schDrawingFlg0": "미포함", "estimate.detail.docPopup.close": "닫기", "estimate.detail.docPopup.docDownload": "문서 다운로드", + "estimate.detail.estimateCopyPopup.title": "견적복사", + "estimate.detail.estimateCopyPopup.explane": "견적서를 복사할 판매점을 설정하십시오. 견적서는 정가로 복사됩니다.", + "estimate.detail.estimateCopyPopup.label.saleStoreId": "1차 판매점명 / ID", + "estimate.detail.estimateCopyPopup.label.otherSaleStoreId": "2차 판매점명 / ID", + "estimate.detail.estimateCopyPopup.label.receiveUser": "담당자", + "estimate.detail.estimateCopyPopup.close": "닫기", + "estimate.detail.estimateCopyPopup.copyBtn": "견적복사", + "estimate.detail.estimateCopyPopup.copy.alertMessage": "견적서가 복사되었습니다. 복사된 물건정보로 이동합니다.", "estimate.detail.productFeaturesPopup.title": "제품특이사항", "estimate.detail.productFeaturesPopup.close": "닫기", + "estimate.detail.productFeaturesPopup.requiredStoreId": "1차 판매점은 필수값 입니다.", + "estimate.detail.productFeaturesPopup.requiredReceiveUser": "담당자는 필수값 입니다.", "estimate.detail.save.alertMsg": "저장되었습니다. 견적서에서 제품을 변경할 경우, 도면 및 회로에 반영되지 않습니다.", - "estimate.detail.save.requiredMsg": "파일첨부가 필수인 아이템이 있습니다. 파일을 첨부하거나 후일첨부를 체크해주십시오.", + "estimate.detail.save.requiredFileUpload": "파일첨부가 필수인 아이템이 있습니다. 파일을 첨부하거나 후일첨부를 체크해주십시오.", + "estimate.detail.save.requiredItem": "제품은 1개이상 등록해야 됩니다.", + "estimate.detail.save.requiredCharger": "담당자는 필수값 입니다.", + "estimate.detail.save.requiredObjectName": "안건명은 필수값 입니다.", + "estimate.detail.save.requiredEstimateDate": "견적일은 필수값 입니다.", + "estimate.detail.save.requiredAmount": "수량은 0보다 큰값을 입력해주세요.", + "estimate.detail.save.requiredSalePrice": "단가는 0보다 큰값을 입력해주세요.", "estimate.detail.reset.confirmMsg": "저장된 견적서 정보가 초기화되고, 도면정보가 반영됩니다. 정말로 초기화 하시겠습니까?", "simulator.title.sub1": "물건번호", "simulator.title.sub2": "작성일", diff --git a/src/store/canvasAtom.js b/src/store/canvasAtom.js index 0242a7b2..9be9f402 100644 --- a/src/store/canvasAtom.js +++ b/src/store/canvasAtom.js @@ -222,6 +222,12 @@ export const adsorptionRangeState = atom({ default: 50, }) +// 도면크기 설정 +export const planSizeSettingState = atom({ + key: 'planSizeSettingMode', + default: { originHorizon: 1600, originVertical: 1600 }, +}) + // 점,선 그리드 설정 export const dotLineGridSettingState = atom({ key: 'gridSettingState', @@ -363,3 +369,24 @@ export const showAngleUnitSelector = selector({ return roofAngleSet === 'slope' ? '寸' : '°' }, }) + +export const moduleSetupSurfaceState = atom({ + key: 'moduleSetupSurfaceState', + default: [], + dangerouslyAllowMutability: true, +}) + +export const moduleInstSurfaceSelector = selector({ + key: 'moduleInstSurfaceSelector', + get: ({ get, parentId }) => { + const moduleSetupSurfaceStateValue = get(moduleSetupSurfaceState) + return moduleSetupSurfaceStateValue.filter((obj) => obj.parentId === parentId) + }, +}) + +//셀 그린 이후에 생성하는 state +export const moduleIsSetupState = atom({ + key: 'moduleIsSetupState', + default: [], + dangerouslyAllowMutability: true, +}) diff --git a/src/store/commonUtilsAtom.js b/src/store/commonUtilsAtom.js index 545f8196..29ef7982 100644 --- a/src/store/commonUtilsAtom.js +++ b/src/store/commonUtilsAtom.js @@ -11,10 +11,6 @@ export const dimensionLineSettingsState = atom({ default: { pixel: 1, color: '#000000', - font: 'Arial', - fontColor: '#000000', - fontSize: 15, - fontStyle: 'normal', }, }) diff --git a/src/store/floorPlanObjectAtom.js b/src/store/floorPlanObjectAtom.js index d514fe54..37227b0a 100644 --- a/src/store/floorPlanObjectAtom.js +++ b/src/store/floorPlanObjectAtom.js @@ -1,7 +1,6 @@ import { atom } from 'recoil' -import { v1 } from 'uuid' export const floorPlanObjectState = atom({ - key: `floorPlanObjectState/${v1()}`, + key: 'floorPlanObjectState', default: { floorPlanObjectNo: '', //물건번호 }, @@ -9,7 +8,7 @@ export const floorPlanObjectState = atom({ }) export const estimateState = atom({ - key: `estimateState`, + key: 'estimateState', default: {}, dangerouslyAllowMutability: true, }) diff --git a/src/store/fontAtom.js b/src/store/fontAtom.js index 1bf0df80..3cc4396c 100644 --- a/src/store/fontAtom.js +++ b/src/store/fontAtom.js @@ -1,10 +1,10 @@ import { atom, selectorFamily } from 'recoil' const defaultFont = { - fontFamily: { name: 'MS PGothic', value: 'MS PGothic' }, - fontWeight: { name: '보통', value: 'normal' }, - fontSize: { name: '16', value: '16' }, - fontColor: { name: '검정색', value: 'black' }, + fontFamily: { id: 1, name: 'MS PGothic', value: 'MS PGothic' }, + fontWeight: { id: 'normal', name: '보통', value: 'normal' }, + fontSize: { id: 16, name: 16, value: 16 }, + fontColor: { id: 'black', name: '검정색', value: 'black' }, } export const globalFontAtom = atom({ diff --git a/src/store/orientationAtom.js b/src/store/orientationAtom.js new file mode 100644 index 00000000..d403c221 --- /dev/null +++ b/src/store/orientationAtom.js @@ -0,0 +1,6 @@ +import { atom } from 'recoil' + +export const compasDegAtom = atom({ + key: 'compasDegAtom', + default: 0, +}) diff --git a/src/store/settingAtom.js b/src/store/settingAtom.js index 869cc004..9e64d72f 100644 --- a/src/store/settingAtom.js +++ b/src/store/settingAtom.js @@ -217,3 +217,11 @@ export const basicSettingState = atom({ ], }, }) + +/** + * 현재 선택된 물건 번호 + */ +export const correntObjectNoState = atom({ + key: 'correntObjectNoState', + default: '', +}) diff --git a/src/store/simulatorAtom.js b/src/store/simulatorAtom.js new file mode 100644 index 00000000..7bdfa363 --- /dev/null +++ b/src/store/simulatorAtom.js @@ -0,0 +1,8 @@ +import { atom } from 'recoil' + +export const pwrGnrSimTypeState = atom({ + key: 'pwrGnrSimType', + default: { + type: 'D', + }, +}) diff --git a/src/store/stuffAtom.js b/src/store/stuffAtom.js index b18fca77..1608beac 100644 --- a/src/store/stuffAtom.js +++ b/src/store/stuffAtom.js @@ -19,6 +19,8 @@ export const stuffSearchState = atom({ startRow: 1, endRow: 100, schSortType: 'R', //정렬조건 (R:최근등록일 U:최근수정일) + pageNo: 1, + pageSize: 100, }, dangerouslyAllowMutability: true, }) diff --git a/src/styles/_contents.scss b/src/styles/_contents.scss index 6715d48b..08b40e93 100644 --- a/src/styles/_contents.scss +++ b/src/styles/_contents.scss @@ -114,6 +114,7 @@ &.btn07{background-image: url(../../public/static/images/canvas/side_icon07.svg);} &.btn08{background-image: url(../../public/static/images/canvas/side_icon08.svg);} &.btn09{background-image: url(../../public/static/images/canvas/side_icon09.svg);} + &.btn10{background-image: url(../../public/static/images/canvas/side_icon10.svg); background-size: 15px 14px;} &:hover{ background-color: #1083E3; } @@ -193,6 +194,7 @@ span{ font-size: 13px; color: #fff; + cursor: pointer; } .control-btn{ display: block; @@ -810,6 +812,7 @@ font-weight: 400; white-space: nowrap; padding-right: 55px; + cursor: pointer; button{ position: absolute; top: 50%; diff --git a/src/styles/_grid-detail.scss b/src/styles/_grid-detail.scss index d6f34b13..4bf432eb 100644 --- a/src/styles/_grid-detail.scss +++ b/src/styles/_grid-detail.scss @@ -59,6 +59,46 @@ .ag-icon-filter::before{ color: #fff; } + .ag-body-vertical-scroll{ + width: 4px !important; + max-width: 4px !important; + min-width: 4px !important; + .ag-body-vertical-scroll-viewport{ + width: 4px !important; + max-width: 4px !important; + min-width: 4px !important; + &::-webkit-scrollbar { + width: 4px; + background-color: transparent; + } + &::-webkit-scrollbar-thumb { + background-color: #C1CCD7; + } + &::-webkit-scrollbar-track { + background-color: transparent; + } + } + } + .ag-body-horizontal-scroll{ + height: 4px !important; + max-height: 4px !important; + min-height: 4px !important; + .ag-body-horizontal-scroll-viewport{ + height: 4px !important; + max-height: 4px !important; + min-height: 4px !important; + &::-webkit-scrollbar { + height: 4px; + background-color: transparent; + } + &::-webkit-scrollbar-thumb { + background-color: #C1CCD7; + } + &::-webkit-scrollbar-track { + background-color: transparent; + } + } + } } .copy-ico-wrap{ display: flex; diff --git a/src/styles/_modal.scss b/src/styles/_modal.scss index 01b47c47..b4987651 100644 --- a/src/styles/_modal.scss +++ b/src/styles/_modal.scss @@ -411,7 +411,6 @@ $alert-color: #101010; .flex-box{ display: flex; align-items: center; - height: 100%; } } &:first-child{ @@ -1910,4 +1909,99 @@ $alert-color: #101010; color: #fff; font-weight: 500; } +} + +// 이미지 불러오기 +.img-flex-box{ + display: flex; + align-items: center; +} +.img-load-from{ + margin-top: 20px; + .img-load-item{ + border-top: 1px solid #424242; + padding: 18px 0; + .d-check-radio{ + margin-bottom: 20px; + } + } + border-bottom: 1px solid #424242; +} + +// 지붕모듈선택 변경 +.module-table-box{ + &.none-flex{ + flex: none; + width: 230px; + } +} +.module-table-flex-wrap{ + &.tab2{ + margin-top: 10px; + gap: 15px; + + } + .module-flex-item{ + flex: 1; + .module-flex-item-tit{ + font-size: 12px; + font-weight: 500; + color: #fff; + padding-bottom: 10px; + border-bottom: 1px solid #4D4D4D; + } + .flex-item-btn-wrap{ + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; + margin-bottom: 24px; + } + &.non-flex{ + display: flex; + flex-direction: column; + justify-content: flex-end; + flex: none; + width: 260px; + } + } +} + +.module-table-box{ + .module-table-inner{ + .module-table-flex-wrap{ + &.tab2{ + .eaves-keraba-table{ + .eaves-keraba-th{ + width: 90px; + } + .eaves-keraba-td{ + padding-left: 0; + padding-bottom: 5px; + } + } + } + } + } +} +.keraba-flex{ + display: flex; + align-items: center; + .grid-select{ + flex: none; + width: 110px; + } + .outline-form{ + justify-content: flex-end; + } +} +.module-bottom{ + padding-bottom: 15px; + border-bottom: 1px solid #4D4D4D; +} + +.reset-word{ + font-size: 12px; + color: #FFCACA; + font-weight: 400; + margin-top: 10px; } \ No newline at end of file diff --git a/src/styles/_reset.scss b/src/styles/_reset.scss index 711ef44a..d87bbc76 100644 --- a/src/styles/_reset.scss +++ b/src/styles/_reset.scss @@ -226,8 +226,8 @@ button{ border: 1px solid #484848; color: #fff; &.blue{ - background-color: #414E6C; - border: 1px solid #414E6C; + background-color: #4C6FBF; + border: 1px solid #4C6FBF; &:hover{ background-color: #414E6C; border: 1px solid #414E6C; @@ -480,6 +480,7 @@ input[type=text]{ border: 1px solid #1083E3; } &::placeholder{ + font-family: 'Noto Sans JP', sans-serif; opacity: 1; font-size: 12px; letter-spacing: 0px; @@ -778,6 +779,12 @@ input[type=text]{ transform: translate(7.75px,4.5px) rotate(45deg); -ms-transform: translate(7.75px,4.5px) rotate(45deg); } + input[type=checkbox]:disabled + label::before{ + background-color: #FAFAFA; + } + input[type=checkbox]:disabled + label::after{ + border-color: #999; + } &.pop{ label{ &:before{ @@ -909,4 +916,61 @@ input[type=text]{ color: #999999; } } +} + +// toggle +.toggle-btn { + position: relative; + display: inline-block; + width: 55px; + height: 20px; + input { + display: none; + } +} +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #454545; + transition: .4s; + border-radius: 10px; + text-align: center; + line-height: 20px; + color: white; + font-size: 12px; + font-weight: 400; + &:after { + content: 'OFF'; + position: absolute; + right: 7px; + color: white; + font-size: 12px; + font-weight: 400; + } + &:before { + position: absolute; + content: ""; + height: 16px; + width: 16px; + left: 2px; + bottom: 2px; + background-color: white; + transition: .4s; + border-radius: 50%; + } +} +input:checked + .slider { + background-color: #2563EB; + &:after { + content: 'ON'; + left: 10px; + right: auto; + } + &:before { + transform: translateX(35px); + } } \ No newline at end of file diff --git a/src/styles/_submodal.scss b/src/styles/_submodal.scss index de01ee36..de53d584 100644 --- a/src/styles/_submodal.scss +++ b/src/styles/_submodal.scss @@ -336,4 +336,36 @@ background-color: transparent; } } +} + +// 견적 복사 +.estimate-copy-info-item{ + margin-bottom: 20px; + &:last-child{ + margin-bottom: 0; + } + .estimate-copy-info-tit{ + font-size: 13px; + color: #101010; + font-weight: 500; + margin-bottom: 10px; + } + .estimate-copy-info-box{ + display: flex; + gap: 5px; + .estimate-copy-sel{ + flex: 1 1 auto; + } + .estimate-copy-id{ + flex: none; + display: flex; + align-items: center; + justify-content: center; + width: 150px; + background-color: #FAFAFA; + border: 1px solid #EEEEEE; + font-size: 13px; + color: #999; + } + } } \ No newline at end of file diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index a78beb15..65c46b74 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -792,7 +792,7 @@ export const rectToPolygon = (rect) => { } //면형상 선택 클릭시 지붕 패턴 입히기 -export function setSurfaceShapePattern(polygon, mode = 'onlyBorder') { +export function setSurfaceShapePattern(polygon, mode = 'onlyBorder', trestleMode = false) { const ratio = window.devicePixelRatio || 1 let width = 265 / 10 @@ -820,6 +820,12 @@ export function setSurfaceShapePattern(polygon, mode = 'onlyBorder') { ctx.lineWidth = mode === 'allPainted' ? 1 : 0.4 ctx.fillStyle = mode === 'allPainted' ? 'rgba(0, 159, 64, 0.7)' : 'white' + if (trestleMode) { + ctx.strokeStyle = 'black' + ctx.lineWidth = 0.2 + ctx.fillStyle = 'rgba(0, 0, 0, 0.1)' + } + if (polygon.direction === 'east' || polygon.direction === 'west') { offset = roofStyle === 1 ? 0 : patternSize.height / 2 for (let col = 0; col <= cols; col++) { @@ -830,7 +836,7 @@ export function setSurfaceShapePattern(polygon, mode = 'onlyBorder') { ctx.moveTo(x, yStart) // 선 시작점 ctx.lineTo(x, yEnd) // 선 끝점 ctx.stroke() - if (mode === 'allPainted') { + if (mode === 'allPainted' || trestleMode) { ctx.fillRect(x, yStart, patternSize.width, yEnd - yStart) } @@ -842,7 +848,7 @@ export function setSurfaceShapePattern(polygon, mode = 'onlyBorder') { ctx.moveTo(xStart, y) // 선 시작점 ctx.lineTo(xEnd, y) // 선 끝점 ctx.stroke() - if (mode === 'allPainted') { + if (mode === 'allPainted' || trestleMode) { ctx.fillRect(xStart, y, xEnd - xStart, patternSize.height) } } @@ -855,7 +861,7 @@ export function setSurfaceShapePattern(polygon, mode = 'onlyBorder') { ctx.moveTo(0, y) // 선 시작점 ctx.lineTo(patternSourceCanvas.width, y) // 선 끝점 ctx.stroke() - if (mode === 'allPainted') { + if (mode === 'allPainted' || trestleMode) { ctx.fillRect(0, y, patternSourceCanvas.width, patternSize.height) } @@ -868,7 +874,7 @@ export function setSurfaceShapePattern(polygon, mode = 'onlyBorder') { ctx.moveTo(x, yStart) // 선 시작점 ctx.lineTo(x, yEnd) // 선 끝점 ctx.stroke() - if (mode === 'allPainted') { + if (mode === 'allPainted' || trestleMode) { ctx.fillRect(x, yStart, patternSize.width, yEnd - yStart) } } @@ -954,3 +960,44 @@ export const getAllRelatedObjects = (id, canvas) => { return result } + +// 모듈,회로 구성에서 사용하는 degree 범위 별 값 +export const getDegreeInOrientation = (degree) => { + if (degree >= 352) { + return 0 + } + if (degree % 15 === 0) return degree + + let value = Math.floor(degree / 15) + const remain = ((degree / 15) % 1).toFixed(5) + + if (remain > 0.4) { + value++ + } + + return value * 15 +} + +export function findAndRemoveClosestPoint(targetPoint, points) { + if (points.length === 0) { + return null + } + + let closestPoint = points[0] + let closestDistance = distanceBetweenPoints(targetPoint, points[0]) + let closestIndex = 0 + + for (let i = 1; i < points.length; i++) { + const distance = distanceBetweenPoints(targetPoint, points[i]) + if (distance < closestDistance) { + closestDistance = distance + closestPoint = points[i] + closestIndex = i + } + } + + // Remove the closest point from the array + points.splice(closestIndex, 1) + + return closestPoint +} diff --git a/src/util/common-utils.js b/src/util/common-utils.js index 998eabd4..5a7ebde4 100644 --- a/src/util/common-utils.js +++ b/src/util/common-utils.js @@ -67,6 +67,13 @@ export const convertNumberToPriceDecimal = (value) => { else return '' } +// 43000.458 --> 43,000.46 +export const convertNumberToPriceDecimalToFixed = (value, fixed) => { + if (value) return Number(value).toLocaleString(undefined, { minimumFractionDigits: fixed, maximumFractionDigits: fixed }) + else if (value === 0) return 0 + else return '' +} + // 전화번호, FAX 번호 숫자 or '-'만 입력 체크 export const inputTelNumberCheck = (e) => { const input = e.target diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index ed375ec5..4f1e9178 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -1201,7 +1201,7 @@ export function removeDuplicatePolygons(polygons) { } export const isSamePoint = (a, b) => { - return Math.abs(Math.round(a.x) - Math.round(b.x)) <= 1 && Math.abs(Math.round(a.y) - Math.round(b.y)) <= 1 + return Math.abs(Math.round(a.x) - Math.round(b.x)) <= 2 && Math.abs(Math.round(a.y) - Math.round(b.y)) <= 2 } /** @@ -1232,6 +1232,444 @@ function calculateAngleBetweenLines(line1, line2) { return (angleInRadians * 180) / Math.PI } +/** + * 박공지붕(templateA, templateB)을 그린다. + * @param roofId + * @param canvas + */ +export const drawGabledRoof = (roofId, canvas) => { + const roof = canvas?.getObjects().find((object) => object.id === roofId) + const roofLines = roof.lines + const wallLines = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roof.id).lines + const hasNonParallelLines = roofLines.filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2) + if (hasNonParallelLines.length > 0) { + alert('대각선이 존재합니다.') + return + } + + const roofPoints = roof.points + const minX = Math.min(...roofPoints.map((point) => point.x)) + const maxX = Math.max(...roofPoints.map((point) => point.x)) + const minY = Math.min(...roofPoints.map((point) => point.y)) + const maxY = Math.max(...roofPoints.map((point) => point.y)) + + // 맞은편 라인을 찾기 위해 현재 polygon으로 만들수 있는 최대한의 길이를 구한다. + const checkLength = Math.abs(Math.sqrt(Math.pow(maxX - minX, 2) + Math.pow(maxY - minY, 2))) + + // 처마라인의 기본속성 입력 + const eaves = [] + roofLines.forEach((currentRoof, index) => { + if (currentRoof.attributes !== undefined && currentRoof.attributes.type === LINE_TYPE.WALLLINE.EAVES) { + eaves.push({ index: index, roof: currentRoof, length: currentRoof.attributes.planeSize }) + } + }) + const ridgeCount = eaves.length - 1 + const ridges = [] + eaves.sort((a, b) => a.length - b.length) + + eaves.forEach((eave, index) => { + if (ridges.length < ridgeCount) { + const index = eave.index, + currentRoof = eave.roof + const currentWall = wallLines[index] + // const prevRoof = index === 0 ? roofLines[wallLines.length - 1] : roofLines[index - 1] + // const nextRoof = index === roofLines.length - 1 ? roofLines[0] : index === roofLines.length ? roofLines[1] : roofLines[index + 1] + + //현재 지붕의 중심 좌표 + const midX = Math.round(((currentRoof.x1 + currentRoof.x2) / 2) * 10) / 10 + const midY = Math.round(((currentRoof.y1 + currentRoof.y2) / 2) * 10) / 10 + const deltaX = currentWall.x2 - currentWall.x1 + const deltaY = currentWall.y2 - currentWall.y1 + const length = Math.sqrt(deltaX * deltaX + deltaY * deltaY) + // currentWall과 직각인 기울기 계산 + const perpendicularDeltaX = deltaY / length + const perpendicularDeltaY = -deltaX / length + // midX와 midY를 기준으로 직각 기울기를 사용하여 midWallX와 midWallY 계산 + const midWallX = midX + perpendicularDeltaX * length + const midWallY = midY + perpendicularDeltaY * length + + const signX = Math.sign(midX - midWallX) + const signY = Math.sign(midY - midWallY) + const checkX = Math.round((midX - signX * checkLength) * 10) / 10 + const checkY = Math.round((midY - signY * checkLength) * 10) / 10 + + const intersectLines = [] + // 현재 지붕의 맞은편 지붕을 찾는다. + roofLines + .filter((line) => line !== currentRoof) + .forEach((line) => { + const intersect = edgesIntersection( + { vertex1: { x: midX, y: midY }, vertex2: { x: checkX, y: checkY } }, + { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, + ) + if (intersect && !intersect.isIntersectionOutside) { + intersectLines.push({ x: intersect.x, y: intersect.y, line: line }) + } + }) + + // 가장 가까운 지붕을 찾는다. + const intersect = intersectLines.reduce((prev, current) => { + if (prev !== undefined) { + const prevDistance = Math.sqrt(Math.pow(prev.x - midX, 2) + Math.pow(prev.y - midY, 2)) + const currentDistance = Math.sqrt(Math.pow(current.x - midX, 2) + Math.pow(current.y - midY, 2)) + return prevDistance >= currentDistance ? current : prev + } else { + return current + } + }, undefined) + + const linesCenterX = Math.round(((midX + intersect.x) / 2) * 10) / 10 + const linesCenterY = Math.round(((midY + intersect.y) / 2) * 10) / 10 + + let addLength = currentRoof.attributes.planeSize / 2 / 10 + + let centerLineX1 = Math.sign(currentRoof.x1 - currentRoof.x2) * addLength + linesCenterX + let centerLineY1 = Math.sign(currentRoof.y1 - currentRoof.y2) * addLength + linesCenterY + let centerLineX2 = Math.sign(currentRoof.x2 - currentRoof.x1) * addLength + linesCenterX + let centerLineY2 = Math.sign(currentRoof.y2 - currentRoof.y1) * addLength + linesCenterY + + const point1Intersect = [] + const point2Intersect = [] + roofLines.forEach((line) => { + const point1 = edgesIntersection( + { vertex1: { x: linesCenterX, y: linesCenterY }, vertex2: { x: centerLineX1, y: centerLineY1 } }, + { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, + ) + if (point1 && !point1.isIntersectionOutside) { + point1Intersect.push({ x: point1.x, y: point1.y }) + } + const point2 = edgesIntersection( + { vertex1: { x: linesCenterX, y: linesCenterY }, vertex2: { x: centerLineX2, y: centerLineY2 } }, + { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, + ) + if (point2 && !point2.isIntersectionOutside) { + point2Intersect.push({ x: point2.x, y: point2.y }) + } + }) + point1Intersect.reduce((prev, current) => { + if (prev !== undefined) { + const prevDistance = Math.sqrt(Math.pow(prev.x - linesCenterX, 2) + Math.pow(prev.y - linesCenterY, 2)) + const currentDistance = Math.sqrt(Math.pow(current.x - linesCenterX, 2) + Math.pow(current.y - linesCenterY, 2)) + return prevDistance >= currentDistance ? current : prev + } else { + return current + } + }, undefined) + point2Intersect.reduce((prev, current) => { + if (prev !== undefined) { + const prevDistance = Math.sqrt(Math.pow(prev.x - linesCenterX, 2) + Math.pow(prev.y - linesCenterY, 2)) + const currentDistance = Math.sqrt(Math.pow(current.x - linesCenterX, 2) + Math.pow(current.y - linesCenterY, 2)) + return prevDistance >= currentDistance ? current : prev + } else { + return current + } + }, undefined) + + if (point1Intersect.length > 0) { + centerLineX1 = point1Intersect[0].x + centerLineY1 = point1Intersect[0].y + } + if (point2Intersect.length > 0) { + centerLineX2 = point2Intersect[0].x + centerLineY2 = point2Intersect[0].y + } + + const ridge = new QLine([centerLineX1, centerLineY1, centerLineX2, centerLineY2], { + fontSize: roof.fontSize, + stroke: 'blue', + strokeWidth: 1, + name: LINE_TYPE.SUBLINE.RIDGE, + attributes: { roofId: roof.id, currentRoof: currentRoof.id }, + }) + canvas.add(ridge) + canvas.renderAll() + ridges.push(ridge) + } + }) + eaves.forEach((eave) => { + const index = eave.index, + currentRoof = eave.roof + const currentWall = wallLines[index] + const prevRoof = index === 0 ? roofLines[wallLines.length - 1] : roofLines[index - 1] + const nextRoof = index === roofLines.length - 1 ? roofLines[0] : index === roofLines.length ? roofLines[1] : roofLines[index + 1] + + const midX = Math.round(((currentRoof.x1 + currentRoof.x2) / 2) * 10) / 10 + const midY = Math.round(((currentRoof.y1 + currentRoof.y2) / 2) * 10) / 10 + const deltaX = currentWall.x2 - currentWall.x1 + const deltaY = currentWall.y2 - currentWall.y1 + const length = Math.sqrt(deltaX * deltaX + deltaY * deltaY) + // currentWall과 직각인 기울기 계산 + const perpendicularDeltaX = deltaY / length + const perpendicularDeltaY = -deltaX / length + // midX와 midY를 기준으로 직각 기울기를 사용하여 midWallX와 midWallY 계산 + const midWallX = midX + perpendicularDeltaX * length + const midWallY = midY + perpendicularDeltaY * length + const signX = Math.sign(midX - midWallX) + const signY = Math.sign(midY - midWallY) + + let points = [] + const intersectRidge = [] + // 현재 roof가 wall보다 작을때 이전, 다음 지붕의 offset만큼 포인트를 조정한다. + if (currentRoof.attributes.planeSize > currentWall.attributes.planeSize) { + points.push({ x: currentRoof.x1, y: currentRoof.y1 }, { x: currentRoof.x2, y: currentRoof.y2 }) + } else if (currentRoof.attributes.planeSize === currentWall.attributes.planeSize) { + const deltaX = currentRoof.x2 - currentRoof.x1 + const deltaY = currentRoof.y2 - currentRoof.y1 + let x1 = currentRoof.x1, + y1 = currentRoof.y1, + x2 = currentRoof.x2, + y2 = currentRoof.y2 + + if (deltaX !== 0) { + const minX = Math.min(currentRoof.x1, currentRoof.x2, currentWall.x1, currentWall.x2) + const maxX = Math.max(currentRoof.x1, currentRoof.x2, currentWall.x1, currentWall.x2) + if (x1 > x2) { + x1 = maxX + x2 = minX + } else { + x1 = minX + x2 = maxX + } + } + if (deltaY !== 0) { + const minY = Math.min(currentRoof.y1, currentRoof.y2, currentWall.y1, currentWall.y2) + const maxY = Math.max(currentRoof.y1, currentRoof.y2, currentWall.y1, currentWall.y2) + if (y1 > y2) { + y1 = maxY + y2 = minY + } else { + y1 = minY + y2 = maxY + } + } + points.push({ x: x1, y: y1 }, { x: x2, y: y2 }) + } else { + const deltaX = currentRoof.x2 - currentRoof.x1 + const deltaY = currentRoof.y2 - currentRoof.y1 + points.push( + { x: currentRoof.x1 - Math.sign(deltaX) * prevRoof.attributes.offset, y: currentRoof.y1 - Math.sign(deltaY) * prevRoof.attributes.offset }, + { x: currentRoof.x2 + Math.sign(deltaX) * nextRoof.attributes.offset, y: currentRoof.y2 + Math.sign(deltaY) * nextRoof.attributes.offset }, + ) + } + ridges.forEach((ridge) => { + const ridgeMidX = (ridge.x1 + ridge.x2) / 2 + const ridgeMidY = (ridge.y1 + ridge.y2) / 2 + if (midX === ridgeMidX || midY === ridgeMidY) { + const intersection = edgesIntersection( + { vertex1: { x: midX, y: midY }, vertex2: { x: ridgeMidX, y: ridgeMidY } }, + { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } }, + ) + if (intersection && !intersection.isIntersectionOutside) { + intersectRidge.push({ line: ridge }) + } + } + if (Math.sign(midX - ridgeMidX) === signX || Math.sign(midY - ridgeMidY) === signY) { + const prevIntersect = edgesIntersection( + { vertex1: { x: prevRoof.x1, y: prevRoof.y1 }, vertex2: { x: prevRoof.x2, y: prevRoof.y2 } }, + { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } }, + ) + const nextIntersect = edgesIntersection( + { vertex1: { x: nextRoof.x1, y: nextRoof.y1 }, vertex2: { x: nextRoof.x2, y: nextRoof.y2 } }, + { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } }, + ) + if (prevIntersect && !prevIntersect.isIntersectionOutside) { + intersectRidge.push({ line: ridge }) + } + if (nextIntersect && !nextIntersect.isIntersectionOutside) { + intersectRidge.push({ line: ridge }) + } + } + }) + + intersectRidge.forEach((intersect, index) => { + const line = intersect.line + const ridge = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { + stroke: 'red', + strokeWidth: 2, + selectable: false, + }) + canvas.add(ridge) + canvas.renderAll() + if (currentRoof.attributes.planeSize > currentWall.attributes.planeSize) { + points.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 }) + } else if (currentRoof.attributes.planeSize === currentWall.attributes.planeSize) { + const deltaX = currentRoof.x2 - currentRoof.x1 + const deltaY = currentRoof.y2 - currentRoof.y1 + let x1 = line.x1, + y1 = line.y1, + x2 = line.x2, + y2 = line.y2 + if (deltaX !== 0) { + const minX = Math.min(currentWall.x1, currentWall.x2, currentRoof.x1, currentRoof.x2, line.x1, line.x2) + const maxX = Math.max(currentWall.x1, currentWall.x2, currentRoof.x1, currentRoof.x2, line.x1, line.x2) + if (x1 > x2) { + x1 = maxX + x2 = minX + } else { + x1 = minX + x2 = maxX + } + } + if (deltaY !== 0) { + const minY = Math.min(currentWall.y1, currentWall.y2, currentRoof.y1, currentRoof.y2, line.y1, line.y2) + const maxY = Math.max(currentWall.y1, currentWall.y2, currentRoof.y1, currentRoof.y2, line.y1, line.y2) + if (y1 > y2) { + y1 = maxY + y2 = minY + } else { + y1 = minY + y2 = maxY + } + } + points.push({ x: x1, y: y1 }, { x: x2, y: y2 }) + } else { + let lineX1 = line.x1, + lineY1 = line.y1, + lineX2 = line.x2, + lineY2 = line.y2 + const prevCheck1 = Math.sqrt(Math.pow(Math.round(lineX1 - currentRoof.x1), 2) + Math.pow(Math.round(lineY1 - currentRoof.y1), 2)) + const prevCheck2 = Math.sqrt(Math.pow(Math.round(lineX2 - currentRoof.x1), 2) + Math.pow(Math.round(lineY2 - currentRoof.y1), 2)) + const deltaX = currentRoof.x2 - currentRoof.x1 + const deltaY = currentRoof.y2 - currentRoof.y1 + if (prevCheck1 < prevCheck2) { + lineX1 = lineX1 - Math.sign(deltaX) * prevRoof.attributes.offset + lineY1 = lineY1 - Math.sign(deltaY) * prevRoof.attributes.offset + lineX2 = lineX2 + Math.sign(deltaX) * nextRoof.attributes.offset + lineY2 = lineY2 + Math.sign(deltaY) * nextRoof.attributes.offset + } else { + lineX1 = lineX1 + Math.sign(deltaX) * prevRoof.attributes.offset + lineY1 = lineY1 + Math.sign(deltaY) * prevRoof.attributes.offset + lineX2 = lineX2 - Math.sign(deltaX) * nextRoof.attributes.offset + lineY2 = lineY2 - Math.sign(deltaY) * nextRoof.attributes.offset + } + points.push({ x: lineX1, y: lineY1 }, { x: lineX2, y: lineY2 }) + } + + canvas.remove(ridge) + canvas.renderAll() + }) + points = points.filter((point, index, self) => index === self.findIndex((p) => p.x === point.x && p.y === point.y)) + + const startPoint = points + .filter((point) => point.x === Math.min(...points.map((point) => point.x))) + .reduce((prev, current) => { + return prev.y < current.y ? prev : current + }) + const sortedPoints = [startPoint] + points.forEach((p, index) => { + const lastPoint = sortedPoints[sortedPoints.length - 1] + if (index === 0) { + const underStartPoint = points.filter((point) => point.y > startPoint.y) + const nextPoint = underStartPoint + .filter((point) => point.x === startPoint.x) + .reduce((prev, current) => { + if (prev === undefined) { + return current + } + return Math.abs(prev.y - startPoint.y) < Math.abs(current.y - startPoint.y) ? prev : current + }, undefined) + if (nextPoint) { + sortedPoints.push(nextPoint) + } else { + const nextPoint = underStartPoint.reduce((prev, current) => { + const prevHypos = Math.sqrt(Math.abs(Math.pow(prev.x - startPoint.x, 2)) + Math.abs(Math.pow(prev.y - startPoint.y, 2))) + const currentHypos = Math.sqrt(Math.abs(Math.pow(current.x - startPoint.x, 2)) + Math.abs(Math.pow(current.y - startPoint.y, 2))) + return prevHypos < currentHypos ? prev : current + }, undefined) + sortedPoints.push(nextPoint) + } + } else { + const prevPoint = sortedPoints[sortedPoints.length - 2] + const otherPoints = points.filter((point) => sortedPoints.includes(point) === false) + const nextPoint = otherPoints.reduce((prev, current) => { + if (prev === undefined) { + const height = Math.abs(Math.sqrt(Math.abs(Math.pow(prevPoint.x - lastPoint.x, 2)) + Math.abs(Math.pow(prevPoint.y - lastPoint.y, 2)))) + const adjacent = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - lastPoint.x, 2)) + Math.abs(Math.pow(current.y - lastPoint.y, 2)))) + const hypotenuse = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - prevPoint.x, 2)) + Math.abs(Math.pow(current.y - prevPoint.y, 2)))) + + const angle = Math.round( + Math.acos((Math.pow(adjacent, 2) + Math.pow(height, 2) - Math.pow(hypotenuse, 2)) / (2 * adjacent * height)) * (180 / Math.PI), + ) + if (angle === 90) { + return current + } + } else { + return prev + } + }, undefined) + if (nextPoint) { + sortedPoints.push(nextPoint) + } else { + const nextPoint = otherPoints.reduce((prev, current) => { + if (prev !== undefined) { + const height = Math.abs(Math.sqrt(Math.abs(Math.pow(prevPoint.x - lastPoint.x, 2)) + Math.abs(Math.pow(prevPoint.y - lastPoint.y, 2)))) + const adjacentC = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - lastPoint.x, 2)) + Math.abs(Math.pow(current.y - lastPoint.y, 2)))) + const hypotenuseC = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - prevPoint.x, 2)) + Math.abs(Math.pow(current.y - prevPoint.y, 2)))) + + const angleC = Math.round( + Math.acos((Math.pow(adjacentC, 2) + Math.pow(height, 2) - Math.pow(hypotenuseC, 2)) / (2 * adjacentC * height)) * (180 / Math.PI), + ) + + const adjacentP = Math.abs(Math.sqrt(Math.abs(Math.pow(prev.x - lastPoint.x, 2)) + Math.abs(Math.pow(prev.y - lastPoint.y, 2)))) + const hypotenuseP = Math.abs(Math.sqrt(Math.abs(Math.pow(prev.x - prevPoint.x, 2)) + Math.abs(Math.pow(prev.y - prevPoint.y, 2)))) + + const angleP = Math.round( + Math.acos((Math.pow(adjacentP, 2) + Math.pow(height, 2) - Math.pow(hypotenuseP, 2)) / (2 * adjacentP * height)) * (180 / Math.PI), + ) + + if (Math.abs(90 - angleC) < Math.abs(90 - angleP)) { + return current + } else { + return prev + } + } else { + return current + } + }, undefined) + if (nextPoint) { + sortedPoints.push(nextPoint) + } + } + } + }) + if (sortedPoints.length > 0) { + const roofPolygon = new QPolygon(sortedPoints, { + fill: 'transparent', + stroke: 'blue', + strokeWidth: 2, + selectable: false, + fontSize: roof.fontSize, + name: 'roofPolygon', + attributes: { + roofId: roof.id, + currentRoof: currentRoof.id, + pitch: currentRoof.attributes.pitch, + degree: currentRoof.attributes.degree, + direction: currentRoof.direction, + }, + }) + const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree + //지붕 각도에 따른 실측치 조정 + roofPolygon.lines.forEach((line) => { + line.attributes.planeSize = Math.round(Math.sqrt(Math.pow(line.x2 - line.x1, 2) + Math.pow(line.y2 - line.y1, 2)) * 10) + const slope = (line) => (line.x2 - line.x1 === 0 ? Infinity : (line.y2 - line.y1) / (line.x2 - line.x1)) + + if (currentDegree > 0 && slope(line) !== slope(currentRoof)) { + const height = Math.tan(currentDegree * (Math.PI / 180)) * line.attributes.planeSize + line.attributes.actualSize = Math.round(Math.sqrt(Math.pow(line.attributes.planeSize, 2) + Math.pow(height, 2))) + } else { + line.attributes.actualSize = line.attributes.planeSize + } + }) + roof.separatePolygon.push(roofPolygon) + canvas.add(roofPolygon) + canvas.renderAll() + } + }) + //기준선 제거 + ridges.forEach((ridge) => canvas.remove(ridge)) +} + /** * 한쪽흐름 지붕 * @param roofId @@ -1246,11 +1684,9 @@ export const drawShedRoof = (roofId, canvas) => { } const sheds = roof.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.SHED) - const eaves = roof.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.EAVES) + // const eaves = roof.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.EAVES) const gables = roof.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.GABLE) - console.log('gable', gables) - let shedDegree = sheds[0].attributes.degree || 0 const shedChon = sheds[0].attributes.pitch || 0 @@ -1311,265 +1747,184 @@ const drawRidge = (roof, canvas) => { // 지붕의 길이가 짧은 순으로 정렬 ridgeRoof.sort((a, b) => a.length - b.length) - console.log('ridgeRoof', ridgeRoof) - ridgeRoof.forEach((item) => { if (getMaxRidge(roofLines.length) > roof.ridges.length) { - let index = item.index, - beforePrevRoof, - prevRoof, - currentRoof = item.roof, - nextRoof, - afterNextRoof + const index = item.index, + currentRoof = item.roof + const prevRoof = index === 0 ? roofLines[wallLines.length - 1] : roofLines[index - 1] + const nextRoof = index === roofLines.length - 1 ? roofLines[0] : index === roofLines.length ? roofLines[1] : roofLines[index + 1] + // const beforePrevRoof = index <= 1 ? roofLines[roofLines.length - 2 + index] : roofLines[index - 2] + // const afterNextRoof = index >= roofLines.length - 2 ? roofLines[(index + 2) % roofLines.length] : roofLines[index + 2] let startXPoint, startYPoint, endXPoint, endYPoint - prevRoof = index === 0 ? roofLines[wallLines.length - 1] : roofLines[index - 1] - nextRoof = index === roofLines.length - 1 ? roofLines[0] : index === roofLines.length ? roofLines[1] : roofLines[index + 1] + // console.log('currentRoof : ', currentRoof.direction, currentRoof.attributes.planeSize) - beforePrevRoof = index <= 1 ? roofLines[roofLines.length - 2 + index] : roofLines[index - 2] - afterNextRoof = index >= roofLines.length - 2 ? roofLines[(index + 2) % roofLines.length] : roofLines[index + 2] + let wallLine = wallLines.filter((w) => w.id === currentRoof.attributes.wallLine) + if (wallLine.length > 0) { + wallLine = wallLine[0] + } const anotherRoof = roofLines.filter((roof) => roof !== currentRoof && roof !== prevRoof && roof !== nextRoof) - let xEqualInnerLines = anotherRoof.filter((roof) => roof.x1 === roof.x2 && isInnerLine(prevRoof, currentRoof, nextRoof, roof)), //x가 같은 내부선 - yEqualInnerLines = anotherRoof.filter((roof) => roof.y1 === roof.y2 && isInnerLine(prevRoof, currentRoof, nextRoof, roof)) //y가 같은 내부선 + let currentX1 = currentRoof.x1, + currentY1 = currentRoof.y1, + currentX2 = currentRoof.x2, + currentY2 = currentRoof.y2 + let ridgeMaxLength = Math.max(prevRoof.attributes.planeSize, nextRoof.attributes.planeSize) / 10 + let ridgeMinLength = Math.min(prevRoof.attributes.planeSize, nextRoof.attributes.planeSize) / 10 - let ridgeBaseLength = Math.round(currentRoof.attributes.planeSize / 2), // 지붕의 기반 길이 - ridgeMaxLength = Math.min(prevRoof.attributes.planeSize, nextRoof.attributes.planeSize), // 지붕의 최대 길이. 이전, 다음 벽 중 짧은 길이 - ridgeAcrossLength = Math.abs(Math.max(prevRoof.attributes.planeSize, nextRoof.attributes.planeSize) - currentRoof.attributes.planeSize) // 맞은편 벽까지의 길이 - 지붕의 기반 길이 + const currentAngle = Math.atan2(currentY2 - currentY1, currentX2 - currentX1) * (180 / Math.PI) + anotherRoof + .filter((roof) => isInnerLine(prevRoof, currentRoof, nextRoof, roof)) + .forEach((innerRoof) => { + const vector1 = { x: currentRoof.x2 - currentRoof.x1, y: currentRoof.y2 - currentRoof.y1 } + const vector2 = { x: innerRoof.x2 - innerRoof.x1, y: innerRoof.y2 - innerRoof.y1 } - console.log('ridgeBaseLength', ridgeBaseLength, 'ridgeMaxLength', ridgeMaxLength, 'ridgeAcrossLength', ridgeAcrossLength) - let acrossRoof = anotherRoof - .filter((roof) => { - const angle1 = calculateAngle(currentRoof.startPoint, currentRoof.endPoint) - const angle2 = calculateAngle(roof.startPoint, roof.endPoint) - if (Math.abs(angle1 - angle2) === 180) { - return roof + const dotProduct = vector1.x * vector2.x + vector1.y * vector2.y + const magnitude1 = Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y) + const magnitude2 = Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y) + const angle = (Math.acos(dotProduct / (magnitude1 * magnitude2)) * 180) / Math.PI + + //현재 지붕선과 직각인 선 + if (Math.abs(angle) === 90) { + const innerBefore = roofLines.find((line) => innerRoof.x1 === line.x2 && innerRoof.y1 === line.y2) + const ibAngle = Math.atan2(innerBefore.y2 - innerBefore.y1, innerBefore.x2 - innerBefore.x1) * (180 / Math.PI) + if (Math.abs(currentAngle - ibAngle) === 180) { + if (currentAngle === 0 || currentAngle === 180) { + currentX2 = innerRoof.x1 + ridgeMinLength = + Math.round( + Math.sqrt( + Math.pow(Math.round(Math.abs(nextRoof.x1 - nextRoof.x2) * 10), 2) + + Math.pow(Math.round(Math.abs(nextRoof.y1 - innerRoof.y2) * 10), 2), + ), + ) / 10 + } + if (currentAngle === 90 || currentAngle === 270) { + currentY2 = innerRoof.y1 + ridgeMinLength = + Math.round( + Math.sqrt( + Math.pow(Math.round(Math.abs(nextRoof.x1 - innerRoof.x2) * 10), 2) + + Math.pow(Math.round(Math.abs(nextRoof.y1 - nextRoof.y2) * 10), 2), + ), + ) / 10 + } + } + if (Math.abs(currentAngle - ibAngle) === 0) { + if (currentAngle === 0 || currentAngle === 180) { + currentX1 = innerRoof.x2 + ridgeMinLength = + Math.round( + Math.sqrt( + Math.pow(Math.round(Math.abs(prevRoof.x1 - prevRoof.x2) * 10), 2) + + Math.pow(Math.round(Math.abs(prevRoof.y1 - innerRoof.y1) * 10), 2), + ), + ) / 10 + } + if (currentAngle === 90 || currentAngle === 270) { + currentY1 = innerRoof.y2 + ridgeMinLength = + Math.round( + Math.sqrt( + Math.pow(Math.round(Math.abs(prevRoof.x1 - innerRoof.x1) * 10), 2) + + Math.pow(Math.round(Math.abs(prevRoof.y1 - prevRoof.y2) * 10), 2), + ), + ) / 10 + } + } + } + //현재 지붕선과 반대인 선 + if (Math.abs(angle) === 180) { + if (currentAngle === 0 || currentAngle === 180) { + } + if (currentAngle === 90 || currentAngle === 270) { + } } }) - .reduce((prev, current) => { - let hasBetweenRoof = false - if (current.x1 === current.x2) { - hasBetweenRoof = roofLines - .filter((roof) => roof !== current) - .some((line) => { - let currentY2 = currentRoof.y2 - if (yEqualInnerLines.length > 0) { - yEqualInnerLines.forEach((line) => { - currentY2 = Math.abs(currentRoof.y1 - currentY2) < Math.abs(currentRoof.y1 - line.y1) ? currentY2 : line.y1 - }) - } - const isY1Between = (line.y1 > currentRoof.y1 && line.y1 < currentY2) || (line.y1 > currentY2 && line.y1 < currentRoof.y1) - const isY2Between = (line.y2 > currentRoof.y1 && line.y2 < currentY2) || (line.y2 > currentY2 && line.y2 < currentRoof.y1) - const isX1Between = (line.x1 > currentRoof.x1 && line.x1 < current.x1) || (line.x1 > currentRoof.x1 && line.x1 < current.x1) - const isX2Between = (line.x2 > currentRoof.x1 && line.x2 < current.x1) || (line.x2 > currentRoof.x1 && line.x2 < current.x1) - return isY1Between && isY2Between && isX1Between && isX2Between - }) - } - if (current.y1 === current.y2) { - hasBetweenRoof = roofLines - .filter((roof) => roof !== current) - .some((line) => { - let currentX2 = currentRoof.x2 - if (xEqualInnerLines.length > 0) { - xEqualInnerLines.forEach((line) => { - currentX2 = Math.abs(currentRoof.x1 - currentX2) < Math.abs(currentRoof.x1 - line.x1) ? currentX2 : line.x1 - }) - } - const isX1Between = (line.x1 > currentRoof.x1 && line.x1 < currentX2) || (line.x1 > currentX2 && line.x1 < currentRoof.x1) - const isX2Between = (line.x2 > currentRoof.x1 && line.x2 < currentX2) || (line.x2 > currentX2 && line.x2 < currentRoof.x1) - const isY1Between = (line.y1 > currentRoof.y1 && line.y1 < current.y1) || (line.y1 > currentRoof.y1 && line.y1 < current.y1) - const isY2Between = (line.y2 > currentRoof.y1 && line.y2 < current.y1) || (line.y2 > currentRoof.y1 && line.y2 < current.y1) + const midX = (currentX1 + currentX2) / 2 // 지붕의 X 중심 + const midY = (currentY1 + currentY2) / 2 // 지붕의 Y 중심 + const alpha = (currentRoof.x1 + currentRoof.x2) / 2 - (wallLine.x1 + wallLine.x2) / 2 // 벽과 지붕의 X 거리 + const beta = (currentRoof.y1 + currentRoof.y2) / 2 - (wallLine.y1 + wallLine.y2) / 2 // 벽과 지붕의 Y 거리 + const hypotenuse = Math.sqrt(Math.pow(alpha, 2) + Math.pow(beta, 2)) // 벽과 지붕의 거리 - return isX1Between && isX2Between && isY1Between && isY2Between - }) - } + const currentPlaneSize = Math.sqrt( + Math.pow(Math.round(Math.abs(currentX1 - currentX2) * 10), 2) + Math.pow(Math.round(Math.abs(currentY1 - currentY2) * 10), 2), + ) + let ridgeBaseLength = Math.round(currentPlaneSize / 2) / 10 // 지붕의 기반 길이 + startXPoint = Math.round(midX + (-1 * (alpha / hypotenuse) * (currentPlaneSize / 2)) / 10) + startYPoint = Math.round(midY + (-1 * (beta / hypotenuse) * (currentPlaneSize / 2)) / 10) + + const checkEndXPoint = Math.round(startXPoint + Math.sign(alpha) * -1 * (ridgeMaxLength + ridgeBaseLength)) + const checkEndYPoint = Math.round(startYPoint + Math.sign(beta) * -1 * (ridgeMaxLength + ridgeBaseLength)) + + const checkLine = new QLine([startXPoint, startYPoint, checkEndXPoint, checkEndYPoint], { + fontSize: roof.fontSize, + stroke: 'red', + strokeWidth: 2, + name: LINE_TYPE.SUBLINE.HIP, + attributes: { roofId: roof.id, currentRoof: currentRoof.id, actualSize: 0 }, + }) + const intersectLines = [] + roofLines.forEach((line) => { + const intersection = edgesIntersection( + { vertex1: { x: checkLine.x1, y: checkLine.y1 }, vertex2: { x: checkLine.x2, y: checkLine.y2 } }, + { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, + ) + if (intersection && !intersection.isIntersectionOutside) { + intersectLines.push({ x: intersection.x, y: intersection.y, line: line }) + } + }) + if (intersectLines.length > 0) { + intersectLines.reduce((prev, current) => { if (prev !== undefined) { - if (currentRoof.x1 === currentRoof.x2) { - return Math.abs(currentRoof.y1 - prev.y1) > Math.abs(currentRoof.y1 - current.y1) ? prev : current - } - if (currentRoof.y1 === currentRoof.y2) { - return Math.abs(currentRoof.x1 - prev.x1) > Math.abs(currentRoof.x1 - current.x1) ? prev : current - } + const prevDistance = Math.sqrt(Math.pow(prev.x - startXPoint, 2) + Math.pow(prev.y - startYPoint, 2)) + const currentDistance = Math.sqrt(Math.pow(current.x - startXPoint, 2) + Math.pow(current.y - startYPoint, 2)) + return prevDistance > currentDistance ? current : prev } else { - if (!hasBetweenRoof) { - if (currentRoof.x1 === currentRoof.x2) { - return Math.sign(currentRoof.y1 - currentRoof.y2) !== Math.sign(current.y1 - current.y2) ? current : undefined - } - if (currentRoof.y1 === currentRoof.y2) { - return Math.sign(currentRoof.x1 - currentRoof.x2) !== Math.sign(current.x1 - current.x2) ? current : undefined - } - return undefined - } else { - return undefined - } + return current } }, undefined) - if (acrossRoof !== undefined) { - if (currentRoof.x1 === currentRoof.x2) { - if (ridgeAcrossLength < Math.abs(currentRoof.x1 - acrossRoof.x1)) { - ridgeAcrossLength = Math.round(Math.round(Math.abs(currentRoof.x1 - acrossRoof.x1) * 10) - currentRoof.attributes.planeSize) - } - } - if (currentRoof.y1 === currentRoof.y2) { - if (ridgeAcrossLength < Math.abs(currentRoof.y1 - acrossRoof.y1)) { - ridgeAcrossLength = Math.round(Math.round(Math.abs(currentRoof.y1 - acrossRoof.y1) * 10) - currentRoof.attributes.planeSize) - } - } } - ridgeBaseLength = ridgeBaseLength / 10 - ridgeMaxLength = ridgeMaxLength / 10 - ridgeAcrossLength = ridgeAcrossLength / 10 + if (intersectLines.length > 0) { + const intersectLine = intersectLines[0] + const diffX = Math.round(intersectLine.x - startXPoint) + const diffY = Math.round(intersectLine.y - startYPoint) + endXPoint = Math.sign(diffX) * Math.round(Math.abs(diffX) - ridgeBaseLength) + startXPoint + endYPoint = Math.sign(diffY) * Math.round(Math.abs(diffY) - ridgeBaseLength) + startYPoint - console.log('ridgeBaseLength', ridgeBaseLength, 'ridgeMaxLength', ridgeMaxLength, 'ridgeAcrossLength', ridgeAcrossLength) + const hypo = Math.sqrt(Math.pow(Math.abs(startXPoint - endXPoint), 2) + Math.pow(Math.abs(startYPoint - endYPoint), 2)) - if (ridgeBaseLength > 0 && ridgeMaxLength > 0 && ridgeAcrossLength > 0) { - let ridgeLength = Math.min(ridgeMaxLength, ridgeAcrossLength) - if (currentRoof.x1 === currentRoof.x2) { - startXPoint = currentRoof.x1 + (nextRoof.direction === 'right' ? 1 : -1) * ridgeBaseLength - startYPoint = currentRoof.y1 + (currentRoof.direction === 'top' ? -1 : 1) * ridgeBaseLength - endXPoint = startXPoint + (nextRoof.direction === 'right' ? 1 : -1) * ridgeLength - endYPoint = startYPoint - let adjustY - if (currentRoof.direction === 'top') { - if (afterNextRoof.direction === 'bottom' && beforePrevRoof.direction === 'bottom') { - adjustY = - Math.abs(currentRoof.x1 - afterNextRoof.x1) < Math.abs(currentRoof.x1 - beforePrevRoof.x1) ? afterNextRoof.y2 : beforePrevRoof.y1 - } else if (afterNextRoof.direction === 'bottom' && afterNextRoof.y2 > currentRoof.y2 && afterNextRoof.y2 < currentRoof.y1) { - adjustY = afterNextRoof.y2 - } else if (beforePrevRoof.direction === 'bottom' && beforePrevRoof.y1 > currentRoof.y2 && beforePrevRoof.y1 < currentRoof.y1) { - adjustY = beforePrevRoof.y1 - } - if (adjustY) { - startYPoint = currentRoof.y1 - Math.abs(currentRoof.y1 - adjustY) / 2 - endYPoint = startYPoint - } - } - if (currentRoof.direction === 'bottom') { - if (afterNextRoof.direction === 'top' && beforePrevRoof.direction === 'top') { - adjustY = - Math.abs(currentRoof.x1 - afterNextRoof.x1) < Math.abs(currentRoof.x1 - beforePrevRoof.x1) ? afterNextRoof.y2 : beforePrevRoof.y1 - } else if (afterNextRoof.direction === 'top' && afterNextRoof.y2 < currentRoof.y2 && afterNextRoof.y2 > currentRoof.y1) { - adjustY = afterNextRoof.y2 - } else if (beforePrevRoof.direction === 'top' && beforePrevRoof.y1 < currentRoof.y2 && beforePrevRoof.y1 > currentRoof.y1) { - adjustY = beforePrevRoof.y1 - } - if (adjustY) { - startYPoint = currentRoof.y1 + Math.abs(currentRoof.y1 - adjustY) / 2 - endYPoint = startYPoint - } - } - if (yEqualInnerLines.length > 0) { - yEqualInnerLines.reduce((prev, current) => { - if (prev !== undefined) { - return Math.abs(currentRoof.y1 - prev.y1) < Math.abs(currentRoof.y1 - current.y1) ? prev : current - } else { - return current - } - }, undefined) - startYPoint = - Math.abs(currentRoof.y1 - startYPoint) * 2 <= Math.abs(currentRoof.y1 - yEqualInnerLines[0].y1) - ? startYPoint - : Math.abs(currentRoof.y1 - yEqualInnerLines[0].y1) - endYPoint = startYPoint - ridgeAcrossLength = Math.max(prevRoof.length, nextRoof.length) - Math.abs(currentRoof.y1 - startYPoint) * 2 - if ( - //yEqualInnerLines 이 다음 벽보다 안쪽에 있을때 - Math.abs(currentRoof.y1 - yEqualInnerLines[0].y1) <= Math.abs(currentRoof.y1 - nextRoof.y1) && - Math.abs(currentRoof.x1 - yEqualInnerLines[0].x2) >= Math.abs(currentRoof.x1 - nextRoof.x2) - ) { - ridgeMaxLength = Math.round(Math.abs(currentRoof.x1 - yEqualInnerLines[0].x2) * 10) / 10 - } - ridgeLength = Math.min(ridgeMaxLength, ridgeAcrossLength) - startXPoint = currentRoof.x1 + (nextRoof.direction === 'right' ? 1 : -1) * Math.abs(currentRoof.y1 - startYPoint) - endXPoint = startXPoint + (nextRoof.direction === 'right' ? 1 : -1) * ridgeLength - } - } - if (currentRoof.y1 === currentRoof.y2) { - startXPoint = currentRoof.x1 + (currentRoof.direction === 'left' ? -1 : 1) * ridgeBaseLength - startYPoint = currentRoof.y1 + (nextRoof.direction === 'bottom' ? 1 : -1) * ridgeBaseLength + const intersectLength = Math.sqrt(Math.pow(Math.abs(midX - intersectLine.x), 2) + Math.pow(Math.abs(midY - intersectLine.y), 2)) + if (intersectLength < prevRoof.attributes.planeSize / 10 && intersectLength < nextRoof.attributes.planeSize / 10) { endXPoint = startXPoint - endYPoint = startYPoint + (nextRoof.direction === 'bottom' ? 1 : -1) * ridgeLength - - let adjustX - if (currentRoof.direction === 'right') { - if (afterNextRoof.direction === 'left' && beforePrevRoof.direction === 'left') { - adjustX = - Math.abs(currentRoof.y1 - afterNextRoof.y1) < Math.abs(currentRoof.y1 - beforePrevRoof.y1) ? afterNextRoof.x2 : beforePrevRoof.x1 - } else if (afterNextRoof.direction === 'left' && afterNextRoof.x2 < currentRoof.x2 && afterNextRoof.x2 > currentRoof.x1) { - adjustX = afterNextRoof.x2 - } else if (beforePrevRoof.direction === 'left' && beforePrevRoof.x1 < currentRoof.x2 && beforePrevRoof.x1 > currentRoof.x1) { - adjustX = beforePrevRoof.x1 - } - if (adjustX) { - startXPoint = currentRoof.x1 + Math.abs(currentRoof.x1 - adjustX) / 2 - endXPoint = startXPoint - } - } - if (currentRoof.direction === 'left') { - if (afterNextRoof.direction === 'right' && beforePrevRoof.direction === 'right') { - adjustX = - Math.abs(currentRoof.y1 - afterNextRoof.y1) < Math.abs(currentRoof.y1 - beforePrevRoof.y1) ? afterNextRoof.x2 : beforePrevRoof.x1 - } else if (afterNextRoof.direction === 'right' && afterNextRoof.x2 > currentRoof.x2 && afterNextRoof.x2 < currentRoof.x1) { - adjustX = afterNextRoof.x2 - } else if (beforePrevRoof.direction === 'right' && beforePrevRoof.x1 > currentRoof.x2 && beforePrevRoof.x1 < currentRoof.x1) { - adjustX = beforePrevRoof.x1 - } - if (adjustX) { - startXPoint = currentRoof.x1 - Math.abs(currentRoof.x1 - adjustX) / 2 - endXPoint = startXPoint - } - } - if (xEqualInnerLines.length > 0) { - xEqualInnerLines.reduce((prev, current) => { - if (prev !== undefined) { - return Math.abs(currentRoof.x1 - prev.x1) < Math.abs(currentRoof.x1 - current.x1) ? prev : current - } else { - return current - } - }, undefined) - startXPoint = - Math.abs(currentRoof.x1 - startXPoint) * 2 <= Math.abs(currentRoof.x1 - xEqualInnerLines[0].x1) - ? startXPoint - : Math.abs(currentRoof.x1 - xEqualInnerLines[0].x1) - endXPoint = startXPoint - ridgeAcrossLength = Math.max(prevRoof.length, nextRoof.length) - Math.abs(currentRoof.x1 - startXPoint) * 2 - if ( - //xEqualInnerLines 이 다음 벽보다 안쪽에 있을때 - Math.abs(currentRoof.x1 - xEqualInnerLines[0].x1) <= Math.abs(currentRoof.x1 - nextRoof.x1) && - Math.abs(currentRoof.y1 - xEqualInnerLines[0].y2) >= Math.abs(currentRoof.y1 - nextRoof.y2) - ) { - ridgeMaxLength = Math.round(Math.abs(currentRoof.y1 - xEqualInnerLines[0].y2) * 10) / 10 - } - ridgeLength = Math.min(ridgeMaxLength, ridgeAcrossLength) - startYPoint = currentRoof.y1 + (nextRoof.direction === 'bottom' ? 1 : -1) * Math.abs(currentRoof.x1 - startXPoint) - endYPoint = startYPoint + (nextRoof.direction === 'bottom' ? 1 : -1) * ridgeLength + endYPoint = startYPoint + } else { + if (ridgeMinLength < hypo) { + endXPoint = Math.round(startXPoint + Math.sign(diffX) * ridgeMinLength) + endYPoint = Math.round(startYPoint + Math.sign(diffY) * ridgeMinLength) } } + } else { + endXPoint = Math.round(startXPoint + Math.sign(alpha) * -1 * ridgeMinLength) + endYPoint = Math.round(startYPoint + Math.sign(beta) * -1 * ridgeMinLength) } - // 마루 그리기 - if (startXPoint !== undefined && startYPoint !== undefined && endXPoint !== undefined && endYPoint !== undefined) { - startXPoint = Math.round(startXPoint * 10) / 10 - startYPoint = Math.round(startYPoint * 10) / 10 - endXPoint = Math.round(endXPoint * 10) / 10 - endYPoint = Math.round(endYPoint * 10) / 10 - const ridge = new QLine( - [Math.min(startXPoint, endXPoint), Math.min(startYPoint, endYPoint), Math.max(startXPoint, endXPoint), Math.max(startYPoint, endYPoint)], - { - fontSize: roof.fontSize, - stroke: 'blue', - strokeWidth: 1, - name: LINE_TYPE.SUBLINE.RIDGE, - attributes: { roofId: roof.id }, - }, - ) - ridge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10) - ridge.attributes.actualSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10) + const ridge = new QLine([startXPoint, startYPoint, endXPoint, endYPoint], { + fontSize: roof.fontSize, + stroke: '#1083E3', + strokeWidth: 2, + name: LINE_TYPE.SUBLINE.RIDGE, + attributes: { roofId: roof.id }, + }) + ridge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10) + ridge.attributes.actualSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10) + + if (ridge.attributes.planeSize > 0) { canvas.add(ridge) roof.ridges.push(ridge) roof.innerLines.push(ridge) @@ -1599,8 +1954,8 @@ const drawRidge = (roof, canvas) => { let y2 = Math.max(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2) const newRidge = new QLine([x1, y1, x2, y2], { fontSize: roof.fontSize, - stroke: 'blue', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: LINE_TYPE.SUBLINE.RIDGE, attributes: { roofId: roof.id }, }) @@ -1682,39 +2037,51 @@ const drawHips = (roof, canvas) => { const nextDegree = nextRoof.attributes.pitch > 0 ? getDegreeByChon(nextRoof.attributes.pitch) : nextRoof.attributes.degree const ridgeCoordinate = currentRoof.attributes.ridgeCoordinate - const hip1 = new QLine([currentRoof.x1, currentRoof.y1, ridgeCoordinate.x1, ridgeCoordinate.y1], { - fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, - name: LINE_TYPE.SUBLINE.HIP, - attributes: { roofId: roof.id, currentRoof: currentRoof.id, actualSize: 0 }, - }) - canvas.add(hip1) - const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10 - const hip1Height = Math.round(hip1Base / Math.tan(((90 - currentDegree) * Math.PI) / 180)) - hip1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip1.x1 - hip1.x2, 2) + Math.pow(hip1.y1 - hip1.y2, 2))) * 10 - if (prevDegree === currentDegree) { - hip1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip1.attributes.planeSize, 2) + Math.pow(hip1Height, 2))) - } - roof.hips.push(hip1) - roof.innerLines.push(hip1) - const hip2 = new QLine([currentRoof.x2, currentRoof.y2, ridgeCoordinate.x1, ridgeCoordinate.y1], { - fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, - name: LINE_TYPE.SUBLINE.HIP, - attributes: { roofId: roof.id, currentRoof: currentRoof.id, actualSize: 0 }, - }) - canvas.add(hip2) - const hip2Base = ((Math.abs(hip2.x1 - hip2.x2) + Math.abs(hip2.y1 - hip2.y2)) / 2) * 10 - const hip2Height = Math.round(hip2Base / Math.tan(((90 - currentDegree) * Math.PI) / 180)) - hip2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip2.x1 - hip2.x2, 2) + Math.pow(hip2.y1 - hip2.y2, 2))) * 10 - if (nextDegree === currentDegree) { - hip2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip2.attributes.planeSize, 2) + Math.pow(hip2Height, 2))) + const vectorX1 = ridgeCoordinate.x1 - currentRoof.x1 + const vectorY1 = ridgeCoordinate.y1 - currentRoof.y1 + const angle1 = Math.atan2(vectorY1, vectorX1) * (180 / Math.PI) + if (Math.abs(Math.round(angle1)) % 45 === 0) { + const hip1 = new QLine([currentRoof.x1, currentRoof.y1, ridgeCoordinate.x1, ridgeCoordinate.y1], { + fontSize: roof.fontSize, + stroke: '#1083E3', + strokeWidth: 2, + name: LINE_TYPE.SUBLINE.HIP, + attributes: { roofId: roof.id, currentRoof: currentRoof.id, actualSize: 0 }, + }) + canvas.add(hip1) + const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10 + const hip1Height = Math.round(hip1Base / Math.tan(((90 - currentDegree) * Math.PI) / 180)) + hip1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip1.x1 - hip1.x2, 2) + Math.pow(hip1.y1 - hip1.y2, 2))) * 10 + if (prevDegree === currentDegree) { + hip1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip1.attributes.planeSize, 2) + Math.pow(hip1Height, 2))) + } + roof.hips.push(hip1) + roof.innerLines.push(hip1) + } + + const vectorX2 = ridgeCoordinate.x1 - currentRoof.x2 + const vectorY2 = ridgeCoordinate.y1 - currentRoof.y2 + const angle2 = Math.atan2(vectorY2, vectorX2) * (180 / Math.PI) + console.log('angle2', Math.abs(Math.round(angle2)) % 45) + if (Math.abs(Math.round(angle2)) % 45 === 0) { + const hip2 = new QLine([currentRoof.x2, currentRoof.y2, ridgeCoordinate.x1, ridgeCoordinate.y1], { + fontSize: roof.fontSize, + stroke: '#1083E3', + strokeWidth: 2, + name: LINE_TYPE.SUBLINE.HIP, + attributes: { roofId: roof.id, currentRoof: currentRoof.id, actualSize: 0 }, + }) + canvas.add(hip2) + const hip2Base = ((Math.abs(hip2.x1 - hip2.x2) + Math.abs(hip2.y1 - hip2.y2)) / 2) * 10 + const hip2Height = Math.round(hip2Base / Math.tan(((90 - currentDegree) * Math.PI) / 180)) + hip2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip2.x1 - hip2.x2, 2) + Math.pow(hip2.y1 - hip2.y2, 2))) * 10 + if (nextDegree === currentDegree) { + hip2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip2.attributes.planeSize, 2) + Math.pow(hip2Height, 2))) + } + roof.hips.push(hip2) + roof.innerLines.push(hip2) } - roof.hips.push(hip2) - roof.innerLines.push(hip2) }) const hipLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.HIP && object.attributes.roofId === roof.id) @@ -1772,8 +2139,8 @@ const drawHips = (roof, canvas) => { if (ridgePoints !== undefined) { const hip = new QLine([currentRoof.x1, currentRoof.y1, ridgePoints.x, ridgePoints.y], { fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, attributes: { roofId: roof.id, currentRoof: currentRoof.id, actualSize: 0 }, }) @@ -1920,8 +2287,8 @@ const connectLinePoint = (polygon) => { const line = new QLine([p.x1, p.y1, p.x2, p.y2], { attributes: { roofId: polygon.id }, fontSize: polygon.fontSize, - stroke: 'purple', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, }) line.attributes.planeSize = Math.round(Math.sqrt(Math.pow(line.x1 - line.x2, 2) + Math.pow(line.y1 - line.y2, 2)) * 10) / 10 line.attributes.actualSize = Math.round(Math.sqrt(Math.pow(line.x1 - line.x2, 2) + Math.pow(line.y1 - line.y2, 2)) * 10) / 10 @@ -1978,8 +2345,8 @@ const connectLinePoint = (polygon) => { const line = new QLine([p.x1, p.y1, p.x2, p.y2], { attributes: { roofId: polygon.id }, fontSize: polygon.fontSize, - stroke: 'purple', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, }) line.attributes.planeSize = Math.round(Math.sqrt(Math.pow(line.x1 - line.x2, 2) + Math.pow(line.y1 - line.y2, 2)) * 10) line.attributes.actualSize = Math.round(Math.sqrt(Math.pow(line.x1 - line.x2, 2) + Math.pow(line.y1 - line.y2, 2)) * 10) @@ -2032,8 +2399,8 @@ const connectLinePoint = (polygon) => { let y2 = Math.max(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2) const newRidge = new QLine([x1, y1, x2, y2], { fontSize: polygon.fontSize, - stroke: 'blue', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: LINE_TYPE.SUBLINE.RIDGE, attributes: { roofId: polygon.id }, }) @@ -2186,10 +2553,8 @@ const changeEavesRoof = (currentRoof, canvas) => { const alpha = midX - midWallX // 벽과 지붕의 X 거리 const beta = midY - midWallY // 벽과 지붕의 Y 거리 const hypotenuse = Math.sqrt(Math.pow(alpha, 2) + Math.pow(beta, 2)) // 벽과 지붕의 거리 - const addHipX2 = Math.sign(midX - midWallX) * (alpha / hypotenuse) * (currentRoof.length / 2) // 추녀마루의 X 너비 - const addHipY2 = Math.sign(midY - midWallY) * (beta / hypotenuse) * (currentRoof.length / 2) // 추녀마루의 Y 너비 - let hipX2 = 0 - let hipY2 = 0 + const hipX2 = Math.round(midX + -1 * (alpha / hypotenuse) * (currentRoof.length / 2)) + const hipY2 = Math.round(midY + -1 * (beta / hypotenuse) * (currentRoof.length / 2)) const innerLines = canvas ?.getObjects() @@ -2267,25 +2632,21 @@ const changeEavesRoof = (currentRoof, canvas) => { const ridge = ridgeLines[0] if (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) { ridge.set({ - x1: midX + addHipX2, - y1: midY + addHipY2, + x1: hipX2, + y1: hipY2, x2: ridge.x2, y2: ridge.y2, }) currentRoof.attributes.ridgeCoordinate = { x1: ridge.x1, y1: ridge.y1 } - hipX2 = midX + addHipX2 - hipY2 = midY + addHipY2 } if (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1) { ridge.set({ x1: ridge.x1, y1: ridge.y1, - x2: midX - addHipX2, - y2: midY - addHipY2, + x2: hipX2, + y2: hipY2, }) currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 } - hipX2 = midX - addHipX2 - hipY2 = midY - addHipY2 } ridge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10) ridge.attributes.actualSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10) @@ -2296,14 +2657,16 @@ const changeEavesRoof = (currentRoof, canvas) => { canvas.remove(hip) }) + canvas?.renderAll() + const prevDegree = prevRoof.attributes.pitch > 0 ? getDegreeByChon(prevRoof.attributes.pitch) : prevRoof.attributes.degree const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree const nextDegree = nextRoof.attributes.pitch > 0 ? getDegreeByChon(nextRoof.attributes.pitch) : nextRoof.attributes.degree const hip1 = new QLine([currentRoof.x1, currentRoof.y1, hipX2, hipY2], { fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, attributes: { roofId: roof.id, @@ -2321,8 +2684,8 @@ const changeEavesRoof = (currentRoof, canvas) => { const hip2 = new QLine([currentRoof.x2, currentRoof.y2, hipX2, hipY2], { fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, attributes: { roofId: roof.id, @@ -2471,8 +2834,8 @@ const changeGableRoof = (currentRoof, canvas) => { let hip1 = new QLine([currentRoof.x1, currentRoof.y1, midX, midY], { fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, attributes: { roofId: roofId, @@ -2484,12 +2847,12 @@ const changeGableRoof = (currentRoof, canvas) => { const hip1Height = Math.round(hip1Base / Math.tan(((90 - prevDegree) * Math.PI) / 180)) hip1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip1.x1 - hip1.x2, 2) + Math.pow(hip1.y1 - hip1.y2, 2))) * 10 hip1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip1.attributes.planeSize, 2) + Math.pow(hip1Height, 2))) - roof.innerLines.push(hip1) + // roof.innerLines.push(hip1) let hip2 = new QLine([currentRoof.x2, currentRoof.y2, midX, midY], { fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, attributes: { roofId: roofId, @@ -2503,7 +2866,11 @@ const changeGableRoof = (currentRoof, canvas) => { const hip2Height = Math.round(hip2Base / Math.tan(((90 - nextDegree) * Math.PI) / 180)) hip2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip2.x1 - hip2.x2, 2) + Math.pow(hip2.y1 - hip2.y2, 2))) * 10 hip2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip2.attributes.planeSize, 2) + Math.pow(hip2Height, 2))) - roof.innerLines.push(hip2) + // roof.innerLines.push(hip2) + hip1.set({ visible: false }) + hip1.setViewLengthText(false) + hip2.set({ visible: false }) + hip2.setViewLengthText(false) canvas?.renderAll() } } @@ -2656,8 +3023,8 @@ const changeHipAndGableRoof = (currentRoof, canvas) => { const hip1 = new QLine([currentRoof.x1, currentRoof.y1, midX + hipX2, midY + hipY2], { fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, attributes: { roofId: roof.id, @@ -2676,8 +3043,8 @@ const changeHipAndGableRoof = (currentRoof, canvas) => { const hip2 = new QLine([currentRoof.x2, currentRoof.y2, midX + hipX2, midY + hipY2], { fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, attributes: { roofId: roof.id, @@ -2711,8 +3078,8 @@ const changeHipAndGableRoof = (currentRoof, canvas) => { hipLines.forEach((hip, i) => { const gableLine = new QLine([hip.x2, hip.y2, currentRoof.attributes.ridgeCoordinate.x1, currentRoof.attributes.ridgeCoordinate.y1], { fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: LINE_TYPE.SUBLINE.GABLE, attributes: { roofId: roof.id, @@ -2897,8 +3264,8 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => { const gable1 = new QLine([midX + hipX1, midY + hipY1, hipX2, hipY2], { fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: LINE_TYPE.SUBLINE.GABLE, attributes: { roofId: roof.id, @@ -2919,8 +3286,8 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => { const gable2 = new QLine([midX + hipX1, midY + hipY1, hipX2, hipY2], { fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: LINE_TYPE.SUBLINE.GABLE, attributes: { roofId: roof.id, @@ -2937,8 +3304,8 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => { const gable3 = new QLine([gable1.x1, gable1.y1, gable2.x1, gable2.y1], { fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: LINE_TYPE.SUBLINE.GABLE, attributes: { roofId: roof.id, @@ -2949,12 +3316,12 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => { gable3.attributes.planeSize = Math.round(Math.sqrt(Math.pow(gable3.x1 - gable3.x2, 2) + Math.pow(gable3.y1 - gable3.y2, 2))) * 10 gable3.attributes.actualSize = Math.round(Math.sqrt(Math.pow(gable3.x1 - gable3.x2, 2) + Math.pow(gable3.y1 - gable3.y2, 2))) * 10 canvas?.add(gable3) - roof.innerLines.push(gable3) + // roof.innerLines.push(gable3) const hip1 = new QLine([currentRoof.x1, currentRoof.y1, gable1.x1, gable1.y1], { fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: LINE_TYPE.SUBLINE.GABLE, attributes: { roofId: roof.id, @@ -2967,12 +3334,12 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => { hip1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip1.x1 - hip1.x2, 2) + Math.pow(hip1.y1 - hip1.y2, 2))) * 10 hip1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip1.attributes.planeSize, 2) + Math.pow(hip1Height, 2))) canvas?.add(hip1) - roof.innerLines.push(hip1) + // roof.innerLines.push(hip1) const hip2 = new QLine([currentRoof.x2, currentRoof.y2, gable2.x1, gable2.y1], { fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, attributes: { roofId: roof.id, @@ -2985,7 +3352,14 @@ const changeJerkInHeadRoof = (currentRoof, canvas) => { hip2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip2.x1 - hip2.x2, 2) + Math.pow(hip2.y1 - hip2.y2, 2))) * 10 hip2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip2.attributes.planeSize, 2) + Math.pow(hip2Height, 2))) canvas?.add(hip2) - roof.innerLines.push(hip2) + // roof.innerLines.push(hip2) + + hip1.set({ visible: false }) + hip1.setViewLengthText(false) + gable3.set({ visible: false }) + gable3.setViewLengthText(false) + hip2.set({ visible: false }) + hip2.setViewLengthText(false) } } } @@ -3120,8 +3494,8 @@ const changeWallRoof = (currentRoof, canvas) => { const addPrevWallLine1 = new QLine([prevX2, prevY2, wallLine.x1 - prevWidthX, wallLine.y1 - prevWidthY], { fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: 'roofLine', attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC }, }) @@ -3130,8 +3504,8 @@ const changeWallRoof = (currentRoof, canvas) => { [addPrevWallLine1.x2, addPrevWallLine1.y2, addPrevWallLine1.x2 + prevWidthX, addPrevWallLine1.y2 + prevWidthY], { fontSize: roof.fontSize, - stroke: 'cyan', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: 'roofLine', attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC }, }, @@ -3139,16 +3513,16 @@ const changeWallRoof = (currentRoof, canvas) => { const addNextWallLine1 = new QLine([wallLine.x2, wallLine.y2, wallLine.x2 + nextWidthX, wallLine.y2 + nextWidthY], { fontSize: roof.fontSize, - stroke: 'green', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: 'roofLine', attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC }, }) const addNextWallLine2 = new QLine([addNextWallLine1.x2, addNextWallLine1.y2, nextX1, nextY1], { fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: 'roofLine', attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC }, }) @@ -3192,8 +3566,8 @@ const changeWallRoof = (currentRoof, canvas) => { let hip1 = new QLine([currentRoof.x1, currentRoof.y1, wallMidX, wallMidY], { fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, attributes: { roofId: roof.id, @@ -3208,8 +3582,8 @@ const changeWallRoof = (currentRoof, canvas) => { let hip2 = new QLine([currentRoof.x2, currentRoof.y2, wallMidX, wallMidY], { fontSize: roof.fontSize, - stroke: 'red', - strokeWidth: 1, + stroke: '#1083E3', + strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, attributes: { roofId: roof.id, diff --git a/yarn.lock b/yarn.lock index ed2cceda..785c8ccb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -529,7 +529,7 @@ "@kurkle/color@^0.3.0": version "0.3.2" - resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f" + resolved "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz" integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw== "@mapbox/node-pre-gyp@^1.0.0": @@ -4344,7 +4344,7 @@ chalk@^2.4.2: chart.js@^4.4.6: version "4.4.6" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.6.tgz#da39b84ca752298270d4c0519675c7659936abec" + resolved "https://registry.npmjs.org/chart.js/-/chart.js-4.4.6.tgz" integrity sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA== dependencies: "@kurkle/color" "^0.3.0" @@ -5846,7 +5846,7 @@ rbush@^3.0.1: react-chartjs-2@^5.2.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz#43c1e3549071c00a1a083ecbd26c1ad34d385f5d" + resolved "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz" integrity sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA== react-color-palette@^7.2.2: @@ -5954,6 +5954,11 @@ react-select@^5.8.1: react-transition-group "^4.3.0" use-isomorphic-layout-effect "^1.1.2" +react-spinners@^0.14.1: + version "0.14.1" + resolved "https://registry.npmjs.org/react-spinners/-/react-spinners-0.14.1.tgz" + integrity sha512-2Izq+qgQ08HTofCVEdcAQCXFEYfqTDdfeDQJeo/HHQiQJD4imOicNLhkfN2eh1NYEWVOX4D9ok2lhuDB0z3Aag== + react-style-singleton@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz" @@ -6280,14 +6285,7 @@ string_decoder@^1.1.1, string_decoder@^1.3.0: dependencies: safe-buffer "~5.2.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==