diff --git a/.gitignore b/.gitignore index 52b4f8d4..f3b61bd7 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,6 @@ next-env.d.ts #lock files yarn.lock -package-lock.json \ No newline at end of file +package-lock.json +pnpm-lock.yaml +certificates \ No newline at end of file diff --git a/MainLayout.codediagram b/MainLayout.codediagram new file mode 100644 index 00000000..e60445db --- /dev/null +++ b/MainLayout.codediagram @@ -0,0 +1 @@ +{"id":-1,"name":"FROM_FILE","userId":-1,"createdAt":"","updatedAt":"","content":{"items":[{"uid":"jsIqfLoyaa","position":{"x":160,"y":360},"sizes":{"width":720,"height":522},"autoheight":true,"blockContent":{"content":[{"type":"filePathNode","attrs":{"pathToFile":"src/app/","version":1},"content":[{"type":"text","marks":[{"type":"bold"}],"text":"layout.js"}]},{"type":"codeBlock","attrs":{"language":"javascript","wrapCode":true},"content":[{"type":"text","text":"\n \n \n {headerPathname === '/login' || headerPathname === '/join' ? (\n {children}\n ) : (\n \n
\n
\n
\n \n {children}\n
\n
\n
\n
\n )}\n \n \n \n \n
"}]}]},"nodeType":"block"},{"uid":"yOseUHcOzU","position":{"x":1020,"y":480},"sizes":{"width":557.5,"height":189.5},"autoheight":true,"blockContent":{"content":[{"type":"filePathNode","attrs":{"pathToFile":"src/app/","version":1},"content":[{"type":"text","marks":[{"type":"bold"}],"text":"RecoilWrapper.js"}]},{"type":"codeBlock","attrs":{"language":"javascript","wrapCode":true},"content":[{"type":"text","text":"export default function RecoilRootWrapper({ children }) {\n return {children}\n}"}]}]},"nodeType":"block"},{"uid":"ohc4COfFLi","position":{"x":1680,"y":370},"sizes":{"width":840.5,"height":312},"autoheight":true,"blockContent":{"content":[{"type":"filePathNode","attrs":{"pathToFile":"src/app/","version":1},"content":[{"type":"text","marks":[{"type":"bold"}],"text":"QcastProvider.js"}]},{"type":"codeBlock","attrs":{"language":"javascript","wrapCode":true},"content":[{"type":"text","text":"<>\n {isGlobalLoading && (\n
\n \n
\n )}\n \n }>{children}\n \n"}]}]},"nodeType":"block"},{"uid":"LQIlkk7G7c","position":{"x":2700,"y":160},"sizes":{"width":400,"height":109},"autoheight":true,"blockContent":{"content":[{"type":"filePathNode","attrs":{"pathToFile":"src/components/header/","version":1},"content":[{"type":"text","marks":[{"type":"bold"}],"text":"Header.jsx"}]},{"type":"paragraph","content":[{"type":"text","text":" "}]}]},"nodeType":"block"},{"uid":"h-RKXEf7EQ","position":{"x":2710,"y":360},"sizes":{"width":792.5,"height":207},"autoheight":true,"blockContent":{"content":[{"type":"filePathNode","attrs":{"pathToFile":"src/app/","version":1},"content":[{"type":"text","marks":[{"type":"bold"}],"text":"SessionProvider.js"}]},{"type":"codeBlock","attrs":{"language":"javascript","wrapCode":true},"content":[{"type":"text","text":"export default function SessionProvider({ useSession, children }) {\n const [session, setSession] = useState(useSession)\n return {children}\n}"}]}]},"nodeType":"block"},{"uid":"eMjqDGJ7H-","position":{"x":2710,"y":660},"sizes":{"width":400,"height":109},"autoheight":true,"blockContent":{"content":[{"type":"filePathNode","attrs":{"pathToFile":"src/components/footer/","version":1},"content":[{"type":"text","marks":[{"type":"bold"}],"text":"Footer.jsx"}]},{"type":"paragraph","content":[{"type":"text","text":" "}]}]},"nodeType":"block"},{"uid":"HjtO7B4Big","position":{"x":1110,"y":730},"sizes":{"width":400,"height":312},"autoheight":true,"blockContent":{"content":[{"type":"filePathNode","attrs":{"pathToFile":"src/components/common/popupManager/","version":1},"content":[{"type":"text","marks":[{"type":"bold"}],"text":"PopupManager.jsx"}]},{"type":"codeBlock","attrs":{"language":"javascript","wrapCode":true},"content":[{"type":"text","text":"export default function PopupManager() {\n const popup = useRecoilValue(popupState)\n\n return [\n ...popup?.config.map((child) => {child.component}),\n ...popup?.other.map((child) => {child.component}),\n ]\n}"}]}]},"nodeType":"block"},{"uid":"VyJRuoccdZ","position":{"x":3620,"y":400},"sizes":{"width":190,"height":95},"autoheight":true,"blockContent":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"type":"text","marks":[{"type":"bold"},{"type":"italic"}],"text":"{children}"}]}]},"nodeType":"block"},{"uid":"BO78hOqExy","position":{"x":1750,"y":140},"sizes":{"width":400,"height":109},"autoheight":true,"blockContent":{"content":[{"type":"filePathNode","attrs":{"pathToFile":"node_modules/next/dist/client/components/","version":1},"content":[{"type":"text","marks":[{"type":"bold"}],"text":"error-boundary.d.ts"}]},{"type":"paragraph","content":[{"type":"text","text":" "}]}]},"nodeType":"block"}],"configs":{"centerX":-105.52521583573127,"centerY":-539.0640896009951,"zoomLevel":1.0999999999999999},"arrowData":{"arrowsMap":{},"pointsMap":{},"edgesMap":{"edge-jsIqfLoyaa-jsIqfLoyaa-right-yOseUHcOzU-yOseUHcOzU-left":{"uid":"edge-jsIqfLoyaa-jsIqfLoyaa-right-yOseUHcOzU-yOseUHcOzU-left","fromNodeId":"jsIqfLoyaa","fromHandleId":"jsIqfLoyaa-right","toNodeId":"yOseUHcOzU","toHandleId":"yOseUHcOzU-left","direction":"ft","selectable":true,"type":"solid","content":{"label":""}},"edge-yOseUHcOzU-yOseUHcOzU-right-ohc4COfFLi-ohc4COfFLi-left":{"uid":"edge-yOseUHcOzU-yOseUHcOzU-right-ohc4COfFLi-ohc4COfFLi-left","fromNodeId":"yOseUHcOzU","fromHandleId":"yOseUHcOzU-right","toNodeId":"ohc4COfFLi","toHandleId":"ohc4COfFLi-left","direction":"ft","selectable":true,"type":"solid","content":{"label":""}},"edge-ohc4COfFLi-ohc4COfFLi-right-LQIlkk7G7c-LQIlkk7G7c-left":{"uid":"edge-ohc4COfFLi-ohc4COfFLi-right-LQIlkk7G7c-LQIlkk7G7c-left","fromNodeId":"ohc4COfFLi","fromHandleId":"ohc4COfFLi-right","toNodeId":"LQIlkk7G7c","toHandleId":"LQIlkk7G7c-left","direction":"ft","selectable":true,"type":"solid","content":{"label":""}},"edge-ohc4COfFLi-ohc4COfFLi-right-h-RKXEf7EQ-h-RKXEf7EQ-left":{"uid":"edge-ohc4COfFLi-ohc4COfFLi-right-h-RKXEf7EQ-h-RKXEf7EQ-left","fromNodeId":"ohc4COfFLi","fromHandleId":"ohc4COfFLi-right","toNodeId":"h-RKXEf7EQ","toHandleId":"h-RKXEf7EQ-left","direction":"ft","selectable":true,"type":"solid","content":{"label":""}},"edge-ohc4COfFLi-ohc4COfFLi-right-eMjqDGJ7H--eMjqDGJ7H--left":{"uid":"edge-ohc4COfFLi-ohc4COfFLi-right-eMjqDGJ7H--eMjqDGJ7H--left","fromNodeId":"ohc4COfFLi","fromHandleId":"ohc4COfFLi-right","toNodeId":"eMjqDGJ7H-","toHandleId":"eMjqDGJ7H--left","direction":"ft","selectable":true,"type":"solid","content":{"label":""}},"edge-yOseUHcOzU-yOseUHcOzU-bottom-HjtO7B4Big-HjtO7B4Big-top":{"uid":"edge-yOseUHcOzU-yOseUHcOzU-bottom-HjtO7B4Big-HjtO7B4Big-top","fromNodeId":"yOseUHcOzU","fromHandleId":"yOseUHcOzU-bottom","toNodeId":"HjtO7B4Big","toHandleId":"HjtO7B4Big-top","direction":"ft","selectable":true,"type":"solid","content":{"label":""}},"edge-h-RKXEf7EQ-h-RKXEf7EQ-right-VyJRuoccdZ-VyJRuoccdZ-left":{"uid":"edge-h-RKXEf7EQ-h-RKXEf7EQ-right-VyJRuoccdZ-VyJRuoccdZ-left","fromNodeId":"h-RKXEf7EQ","fromHandleId":"h-RKXEf7EQ-right","toNodeId":"VyJRuoccdZ","toHandleId":"VyJRuoccdZ-left","direction":"ft","selectable":true,"type":"solid","content":{"label":""}},"edge-BO78hOqExy-BO78hOqExy-bottom-ohc4COfFLi-ohc4COfFLi-top":{"uid":"edge-BO78hOqExy-BO78hOqExy-bottom-ohc4COfFLi-ohc4COfFLi-top","fromNodeId":"BO78hOqExy","fromHandleId":"BO78hOqExy-bottom","toNodeId":"ohc4COfFLi","toHandleId":"ohc4COfFLi-top","direction":"ft","selectable":true,"type":"solid","content":{"label":""}}}}}} \ No newline at end of file diff --git a/package.json b/package.json index 1f272ce1..4fca5d9a 100644 --- a/package.json +++ b/package.json @@ -6,13 +6,15 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "serve": "node server.js" }, "dependencies": { "@nextui-org/react": "^2.4.2", "ag-grid-react": "^32.0.2", "axios": "^1.7.8", "chart.js": "^4.4.6", + "dayjs": "^1.11.13", "fabric": "^5.3.0", "framer-motion": "^11.2.13", "fs": "^0.0.1-security", @@ -20,7 +22,7 @@ "js-cookie": "^3.0.5", "mathjs": "^13.0.2", "mssql": "^11.0.1", - "next": "14.2.14", + "next": "14.2.21", "next-international": "^1.2.4", "react": "^18", "react-chartjs-2": "^5.2.0", @@ -32,12 +34,12 @@ "react-icons": "^5.3.0", "react-loading-skeleton": "^3.5.0", "react-responsive-modal": "^6.4.2", + "react-select": "^5.8.1", "recoil": "^0.7.7", "sweetalert2": "^11.14.1", "sweetalert2-react-content": "^5.0.7", - "uuid": "^10.0.0", - "dayjs": "^1.11.13", - "react-select": "^5.8.1" + "usehooks-ts": "^3.1.0", + "uuid": "^10.0.0" }, "devDependencies": { "@turf/turf": "^7.0.0", diff --git a/server.js b/server.js new file mode 100644 index 00000000..43bc0266 --- /dev/null +++ b/server.js @@ -0,0 +1,40 @@ +const http = require('http') +const { parse } = require('url') +const next = require('next') + +const https = require('https') +const fs = require('fs') + +const dev = process.env.NODE_ENV !== 'production' +const app = next({ dev }) +const handle = app.getRequestHandler() + +const PORT = 3000 + +const httpsOptions = { + key: fs.readFileSync('./certificates/key.pem'), + cert: fs.readFileSync('./certificates/cert.pem'), +} + +app.prepare().then(() => { + http + .createServer((req, res) => { + const parsedUrl = parse(req.url, true) + handle(req, res, parsedUrl) + }) + .listen(PORT, (err) => { + if (err) throw err + console.log(`> Ready on http://localhost:${PORT}`) + }) + + // https 서버 추가 + https + .createServer(httpsOptions, (req, res) => { + const parsedUrl = parse(req.url, true) + handle(req, res, parsedUrl) + }) + .listen(PORT + 1, (err) => { + if (err) throw err + console.log(`> HTTPS: Ready on https://localhost:${PORT + 1}`) + }) +}) diff --git a/src/app/GlobalDataProvider.js b/src/app/GlobalDataProvider.js new file mode 100644 index 00000000..e4162cd1 --- /dev/null +++ b/src/app/GlobalDataProvider.js @@ -0,0 +1,24 @@ +'use client' + +import { createContext, useEffect, useState } from 'react' +import { useLocalStorage } from 'usehooks-ts' + +export const GlobalDataContext = createContext({ + managementState: {}, + setManagementState: () => {}, + managementStateLoaded: null, +}) + +const GlobalDataProvider = ({ children }) => { + const [managementState, setManagementState] = useState({}) + // TODO: 임시 조치이며 개발 완료시 삭제 예정 -> 잊지말기... + const [managementStateLoaded, setManagementStateLoaded] = useLocalStorage('managementStateLoaded', null) + + useEffect(() => { + setManagementStateLoaded(managementState) + }, [managementState]) + + return {children} +} + +export default GlobalDataProvider diff --git a/src/app/QcastProvider.js b/src/app/QcastProvider.js index 16a62d63..43404308 100644 --- a/src/app/QcastProvider.js +++ b/src/app/QcastProvider.js @@ -1,9 +1,8 @@ 'use client' -import { createContext, useEffect, useState } from 'react' +import { createContext, 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' @@ -19,7 +18,6 @@ export const QcastContext = createContext({ 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({ @@ -30,16 +28,6 @@ export const QcastProvider = ({ children }) => { businessChargerMail: null, }) - useEffect(() => { - const targetElement = document.getElementById('canvas') - if (!targetElement && currentCanvasPlan?.id && planSave) { - setPlanSave((prev) => !prev) - checkUnsavedCanvasPlan() - } else if (targetElement && currentCanvasPlan?.id) { - setPlanSave(true) - } - }, [modifiedPlans]) - // useEffect(() => { // console.log('commonCode', commonCode) // console.log(findCommonCode(113600)) diff --git a/src/app/layout.js b/src/app/layout.js index 3383aa10..4385f2f8 100644 --- a/src/app/layout.js +++ b/src/app/layout.js @@ -4,10 +4,11 @@ import { getSession } from '@/lib/authActions' import RecoilRootWrapper from './RecoilWrapper' import { QcastProvider } from './QcastProvider' +import SessionProvider from './SessionProvider' +import GlobalDataProvider from './GlobalDataProvider' import Header from '@/components/header/Header' import QModal from '@/components/common/modal/QModal' import Dimmed from '@/components/ui/Dimmed' -import SessionProvider from './SessionProvider' import PopupManager from '@/components/common/popupManager/PopupManager' import './globals.css' @@ -50,7 +51,6 @@ export default async function RootLayout({ children }) { isLoggedIn: session.isLoggedIn, } } - if (!headerPathname.includes('/login') && !session.isLoggedIn) { redirect('/login') } @@ -61,26 +61,28 @@ export default async function RootLayout({ children }) { return ( - - - {headerPathname === '/login' || headerPathname === '/join' ? ( - {children} - ) : ( - -
-
-
- - {children} + + + + {headerPathname === '/login' || headerPathname === '/join' ? ( + {children} + ) : ( + +
+
+
+ + {children} +
+
-
-
- - )} - - - - + + )} + + + + + ) } diff --git a/src/app/management/ManagementProvider.js b/src/app/management/ManagementProvider.js index 197b30c0..fb57e948 100644 --- a/src/app/management/ManagementProvider.js +++ b/src/app/management/ManagementProvider.js @@ -1,20 +1,18 @@ 'ues client' -import { createContext, useEffect, useState } from 'react' +import { createContext } from 'react' -export const ManagementContext = createContext({ - managementState: {}, - setManagementState: () => {}, -}) +export const ManagementContext = createContext({}) const ManagementProvider = ({ children }) => { - const [managementState, setManagementState] = useState({}) + // const [managementState, setManagementState] = useState({}) - useEffect(() => { - console.log('🚀 ~ managementState:', managementState) - }, [managementState]) + // useEffect(() => { + // console.log('🚀 ~ managementState:', managementState) + // }, [managementState]) - return {children} + // return {children} + return {children} } export default ManagementProvider diff --git a/src/common/common.js b/src/common/common.js index e2e31889..a28542e9 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -112,6 +112,7 @@ export const POLYGON_TYPE = { WALL: 'wall', TRESTLE: 'trestle', MODULE_SETUP_SURFACE: 'moduleSetupSurface', + MODULE: 'module', } export const SAVE_KEY = [ diff --git a/src/components/Main.jsx b/src/components/Main.jsx index 1a436582..5f6c1052 100644 --- a/src/components/Main.jsx +++ b/src/components/Main.jsx @@ -41,12 +41,12 @@ export default function MainPage(mainPageProps) { if (searchRadioType === 'object') { setStuffSearch({ ...stuffSearch, - schObjectNo: searchTxt, + schObjectNo: searchTxt.trim(), code: 'M', }) router.push('/management/stuff', { scroll: false }) } else { - setSearchForm({ ...searchForm, searchValue: searchTxt, mainFlag: 'Y' }) + setSearchForm({ ...searchForm, searchValue: searchTxt.trim(), mainFlag: 'Y' }) router.push('/community/faq') } } diff --git a/src/components/Playground.jsx b/src/components/Playground.jsx index e3d185ec..1d5820ff 100644 --- a/src/components/Playground.jsx +++ b/src/components/Playground.jsx @@ -1,27 +1,28 @@ 'use client' -import { useRef, useState, useEffect } from 'react' +import { useRef, useState, useEffect, useContext } from 'react' +import Image from 'next/image' import { useRecoilState } from 'recoil' import { v4 as uuidv4 } from 'uuid' import { FaAnglesUp } from 'react-icons/fa6' import { FaAnglesDown } from 'react-icons/fa6' +import { Button } from '@nextui-org/react' +import ColorPicker from './common/color-picker/ColorPicker' +import { cadFileNameState, googleMapFileNameState, useCadFileState, useGoogleMapFileState } from '@/store/canvasAtom' import { useAxios } from '@/hooks/useAxios' import { useMessage } from '@/hooks/useMessage' import { useMasterController } from '@/hooks/common/useMasterController' -import { convertDwgToPng } from '@/lib/cadAction' -import { cadFileNameState, googleMapFileNameState, useCadFileState, useGoogleMapFileState } from '@/store/canvasAtom' - -import { Button } from '@nextui-org/react' -import ColorPicker from './common/color-picker/ColorPicker' import { useSwal } from '@/hooks/useSwal' - -import styles from './playground.module.css' -import Image from 'next/image' - +import { convertDwgToPng } from '@/lib/cadAction' +import { GlobalDataContext } from '@/app/GlobalDataProvider' import QInput from './common/input/Qinput' import QSelect from './common/select/QSelect' import QPagination from './common/pagination/QPagination' +import QSelectBox from './common/select/QSelectBox' +import SampleReducer from './sample/SampleReducer' + +import styles from './playground.module.css' export default function Playground() { const [useCadFile, setUseCadFile] = useRecoilState(useCadFileState) @@ -36,7 +37,7 @@ export default function Playground() { const converterUrl = process.env.NEXT_PUBLIC_CONVERTER_API_URL const { getMessage } = useMessage() const { swalFire } = useSwal() - const { getRoofMaterialList, getModuleTypeItemList } = useMasterController() + const { getRoofMaterialList, getModuleTypeItemList, getTrestleList, getConstructionList, getTrestleDetailList } = useMasterController() const [color, setColor] = useState('#ff0000') @@ -48,6 +49,8 @@ export default function Playground() { const [users, setUsers] = useState([]) + const { managementState, setManagementState, managementStateLoaded } = useContext(GlobalDataContext) + useEffect(() => { console.log('textInput:', textInput) }, [textInput]) @@ -154,6 +157,75 @@ export default function Playground() { console.log('users:', users) }, [users]) + const codes = [ + { + clHeadCd: '203800', + clCode: 'HEI_455', + clCodeNm: '세로 455mm이하', + clPriority: 1, + name: '세로 455mm이하', + id: 'HEI_455', + }, + { + clHeadCd: '203800', + clCode: 'HEI_500', + clCodeNm: '세로 500mm이하', + clPriority: 2, + name: '세로 500mm이하', + id: 'HEI_500', + }, + { + clHeadCd: '203800', + clCode: 'HEI_606', + clCodeNm: '세로 606mm이하', + clPriority: 3, + name: '세로 606mm이하', + id: 'HEI_606', + }, + { + clHeadCd: '203800', + clCode: 'WID_606', + clCodeNm: '가로 606mm이하', + clPriority: 4, + name: '가로 606mm이하', + id: 'WID_606', + }, + { + clHeadCd: '203800', + clCode: 'ETC', + clCodeNm: '기타', + clPriority: 5, + name: '기타', + id: 'ETC', + }, + ] + + const [myData, setMyData] = useState({ + roofMatlCd: 'ROOF_ID_WA_53A', + roofMatlNm: '화와 A', + roofMatlNmJp: '和瓦A', + widAuth: 'R', + widBase: '265.000', + lenAuth: 'R', + lenBase: '235.000', + roofPchAuth: null, + roofPchBase: null, + raftAuth: 'C', + raftBaseCd: 'HEI_455', + id: 'ROOF_ID_WA_53A', + name: '화와 A', + selected: true, + nameJp: '和瓦A', + length: 235, + width: 265, + layout: 'P', + hajebichi: null, + }) + + const handleChangeMyData = () => { + setMyData({ ...myData, raftBaseCd: 'HEI_500' }) + } + return ( <>
@@ -166,16 +238,70 @@ export default function Playground() { }} > 지붕재 목록 조회 API 호출 - + {' '} {' '} + {' '} + {' '} +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+

{managementStateLoaded?.objectNo}

+
+
+ +
) diff --git a/src/components/auth/Login.jsx b/src/components/auth/Login.jsx index af50a515..55711929 100644 --- a/src/components/auth/Login.jsx +++ b/src/components/auth/Login.jsx @@ -5,7 +5,7 @@ import Image from 'next/image' import Link from 'next/link' import { useRecoilState } from 'recoil' import { useAxios } from '@/hooks/useAxios' -import { setSession } from '@/lib/authActions' +import { setSession, login } from '@/lib/authActions' import { useMessage } from '@/hooks/useMessage' import { globalLocaleStore } from '@/store/localeAtom' import { sessionStore } from '@/store/commonAtom' @@ -36,7 +36,7 @@ export default function Login() { const result = { ...response, storeLvl: response.groupId === '60000' ? '1' : '2', pwdInitYn: 'Y' } setSession(result) setSessionState(result) - router.push('/') + login() } else { router.push('/login') } @@ -97,7 +97,8 @@ export default function Login() { } else { Cookies.remove('chkLoginId') } - router.push('/') + // router.push('/') + login() } else { alert(res.data.result.resultMsg) } diff --git a/src/components/common/color-picker/ColorPickerModal.jsx b/src/components/common/color-picker/ColorPickerModal.jsx index 62abd2e2..66c0e0b7 100644 --- a/src/components/common/color-picker/ColorPickerModal.jsx +++ b/src/components/common/color-picker/ColorPickerModal.jsx @@ -66,7 +66,7 @@ export default function ColorPickerModal(props) { //치수선색설정 아닐 때만 바로 저장 실행 if (name !== 'DimensionLineColor') - setSettingsData({ + setSettingsDataSave({ ...settingsData, color: originColor, }) diff --git a/src/components/common/popupManager/PopupManager.jsx b/src/components/common/popupManager/PopupManager.jsx index e84ee610..147e0988 100644 --- a/src/components/common/popupManager/PopupManager.jsx +++ b/src/components/common/popupManager/PopupManager.jsx @@ -1,8 +1,11 @@ 'use client' +import { Fragment } from 'react' import { useRecoilValue } from 'recoil' import { popupState } from '@/store/popupAtom' -import { Fragment } from 'react' +/** + * 팝업 관리자 + */ export default function PopupManager() { const popup = useRecoilValue(popupState) diff --git a/src/components/common/select/QSelectBox.jsx b/src/components/common/select/QSelectBox.jsx index 3da9f30b..9f86474d 100644 --- a/src/components/common/select/QSelectBox.jsx +++ b/src/components/common/select/QSelectBox.jsx @@ -1,26 +1,81 @@ 'use client' -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' +import { useOnClickOutside } from 'usehooks-ts' + +/** + * + * @param {string} title - 선택 제목 (선택이 없을때 보여질 값) + * @param {array} options - 선택 옵션 객체 {} + * @param {function} onChange - 선택 변경 함수 + * @param {object} value - 선택 값 객체 {} + * @param {boolean} disabled - 선택 비활성화 여부 + * @param {string} sourceKey - options에 있는 키 + * @param {string} targetKey - value에 있는 키 + * @param {string} showKey - options 있는 키중 보여줄 키 + * @param {object} params - 추가 파라미터 + * @returns + */ +export default function QSelectBox({ + title = '', + options, + onChange, + value, + disabled = false, + sourceKey = '', + targetKey = '', + showKey = '', + params = {}, +}) { + /** + * 초기 상태 처리 + * useState 초기 값으로 사용해야 해서 useState 보다 위에 작성 + * @returns {string} 초기 상태 + */ + const handleInitState = () => { + //title이 있으면 우선 보여준다(다른 키들 무시) + if (title !== '') { + return title + } + + //value가 없으면 showKey가 있으면 우선 보여준다 + if (showKey !== '' && !value) { + return options[0][showKey] + } + + //value가 있으면 sourceKey와 targetKey를 비교하여 보여준다 + if (showKey !== '' && value) { + const option = options.find((option) => option[sourceKey] === value[targetKey]) + return option[showKey] + } + } -export default function QSelectBox({ title = '', options, onChange, value, disabled = false, params = {} }) { const [openSelect, setOpenSelect] = useState(false) - const [selected, setSelected] = useState(title === '' ? options[0].name : title) + const [selected, setSelected] = useState(handleInitState()) + const ref = useRef(null) const handleClickSelectOption = (option) => { - setSelected(option.name) + setSelected(showKey !== '' ? option[showKey] : option.name) onChange?.(option, params) } + const handleClose = () => { + setOpenSelect(false) + } + useEffect(() => { - value && handleClickSelectOption(value) - }, [value]) + // value && handleClickSelectOption(value) + setSelected(handleInitState()) + }, [value, sourceKey, targetKey, showKey]) + + useOnClickOutside(ref, handleClose) return ( -
{} : () => setOpenSelect(!openSelect)}> +
{} : () => setOpenSelect(!openSelect)}>

{selected}

    {options?.map((option, index) => (
  • handleClickSelectOption(option)}> - +
  • ))}
diff --git a/src/components/fabric/QLine.js b/src/components/fabric/QLine.js index 2a0bbca0..65575731 100644 --- a/src/components/fabric/QLine.js +++ b/src/components/fabric/QLine.js @@ -35,10 +35,6 @@ export const QLine = fabric.util.createClass(fabric.Line, { this.startPoint = { x: this.x1, y: this.y1 } this.endPoint = { x: this.x2, y: this.y2 } - - if (canvas) { - this.canvas = canvas - } }, init: function () { diff --git a/src/components/floor-plan/CanvasFrame.jsx b/src/components/floor-plan/CanvasFrame.jsx index cd39cda0..cf2e0d56 100644 --- a/src/components/floor-plan/CanvasFrame.jsx +++ b/src/components/floor-plan/CanvasFrame.jsx @@ -1,32 +1,28 @@ 'use client' -import { useContext, useEffect, useRef } from 'react' +import { useEffect, useRef } from 'react' import { useRecoilValue } from 'recoil' +import QContextMenu from '@/components/common/context-menu/QContextMenu' +import PanelBatchStatistics from '@/components/floor-plan/modal/panelBatch/PanelBatchStatistics' +import ImgLoad from '@/components/floor-plan/modal/ImgLoad' import { useCanvas } from '@/hooks/useCanvas' -import { useEvent } from '@/hooks/useEvent' import { usePlan } from '@/hooks/usePlan' import { useContextMenu } from '@/hooks/useContextMenu' -import { currentMenuState } from '@/store/canvasAtom' -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 { currentMenuState } from '@/store/canvasAtom' import { totalDisplaySelector } from '@/store/settingAtom' -import ImgLoad from '@/components/floor-plan/modal/ImgLoad' +import { MENU } from '@/common/common' export default function CanvasFrame() { const canvasRef = useRef(null) - const { canvas, handleBackImageLoadToCanvas } = useCanvas('canvas') + const { canvas } = useCanvas('canvas') const { canvasLoadInit, gridInit } = useCanvasConfigInitialize() const currentMenu = useRecoilValue(currentMenuState) const { contextMenu, handleClick } = useContextMenu() - const { selectedPlan, modifiedPlanFlag, checkCanvasObjectEvent, resetModifiedPlans, currentCanvasPlan } = usePlan() + const { selectedPlan } = usePlan() const totalDisplay = useRecoilValue(totalDisplaySelector) // 집계표 표시 여부 - // useEvent() - // const { initEvent } = useContext(EventContext) - // initEvent() const loadCanvas = () => { if (canvas) { @@ -41,21 +37,8 @@ export default function CanvasFrame() { } } - useEffect(() => { - if (modifiedPlanFlag && selectedPlan?.id) { - checkCanvasObjectEvent(selectedPlan.id) - } - }, [modifiedPlanFlag]) - - useEffect(() => { - return () => { - resetModifiedPlans() - } - }, []) - useEffect(() => { loadCanvas() - resetModifiedPlans() }, [selectedPlan, canvas]) return ( diff --git a/src/components/floor-plan/CanvasLayout.jsx b/src/components/floor-plan/CanvasLayout.jsx index 9e48873e..ba2994c0 100644 --- a/src/components/floor-plan/CanvasLayout.jsx +++ b/src/components/floor-plan/CanvasLayout.jsx @@ -3,12 +3,12 @@ import { useContext, useEffect } from 'react' import { useRecoilValue } from 'recoil' import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider' +import { SessionContext } from '@/app/SessionProvider' import { useMessage } from '@/hooks/useMessage' import { useSwal } from '@/hooks/useSwal' import { usePlan } from '@/hooks/usePlan' -import { globalLocaleStore } from '@/store/localeAtom' -import { SessionContext } from '@/app/SessionProvider' import { useCanvasMenu } from '@/hooks/common/useCanvasMenu' +import { globalLocaleStore } from '@/store/localeAtom' export default function CanvasLayout({ children }) { // const { menuNumber } = props @@ -20,7 +20,7 @@ export default function CanvasLayout({ children }) { const { getMessage } = useMessage() const { swalFire } = useSwal() - const { plans, modifiedPlans, loadCanvasPlanData, handleCurrentPlan, handleAddPlan, handleDeletePlan } = usePlan() + const { plans, loadCanvasPlanData, handleCurrentPlan, handleAddPlan, handleDeletePlan } = usePlan() useEffect(() => { loadCanvasPlanData(session.userId, objectNo, pid) @@ -36,27 +36,31 @@ export default function CanvasLayout({ children }) { className={`canvas-page-box ${plan.isCurrent === true ? 'on' : ''}`} onClick={() => handleCurrentPlan(plan.id)} > - - {`Plan ${plan.ordering}`} - {modifiedPlans.some((modifiedPlan) => modifiedPlan === plan.id) && ' [ M ]'} - - - swalFire({ - text: `Plan ${plan.ordering} ` + getMessage('plan.message.confirm.delete'), - type: 'confirm', - confirmFn: () => { - handleDeletePlan(e, plan.id) - }, - }) - } - > + {`Plan ${plan.ordering}`} + {plan.ordering !== 1 && ( + + swalFire({ + text: `Plan ${plan.ordering} ` + getMessage('plan.message.confirm.delete'), + type: 'confirm', + confirmFn: () => { + handleDeletePlan(e, plan.id) + }, + }) + } + > + )} ))}
{plans.length < 10 && ( - )} diff --git a/src/components/floor-plan/CanvasMenu.jsx b/src/components/floor-plan/CanvasMenu.jsx index 435fc762..cd655841 100644 --- a/src/components/floor-plan/CanvasMenu.jsx +++ b/src/components/floor-plan/CanvasMenu.jsx @@ -2,47 +2,47 @@ import { useContext, useEffect, useState } from 'react' +import { usePathname, useRouter } from 'next/navigation' + import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' -import { usePathname, useRouter } from 'next/navigation' +import { v4 as uuidv4 } from 'uuid' + import MenuDepth01 from './MenuDepth01' import QSelectBox from '@/components/common/select/QSelectBox' -import { v4 as uuidv4 } from 'uuid' +import SettingModal01 from '@/components/floor-plan/modal/setting01/SettingModal01' +import PlacementShapeSetting from '@/components/floor-plan/modal/placementShape/PlacementShapeSetting' +import EstimateCopyPop from '../estimate/popup/EstimateCopyPop' +import DocDownOptionPop from '../estimate/popup/DocDownOptionPop' +import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider' import { useMessage } from '@/hooks/useMessage' import { usePlan } from '@/hooks/usePlan' import { useSwal } from '@/hooks/useSwal' import { useEvent } from '@/hooks/useEvent' +import { usePopup } from '@/hooks/usePopup' +import { useCanvasEvent } from '@/hooks/useCanvasEvent' +import { useCommonUtils } from '@/hooks/common/useCommonUtils' +import useMenu from '@/hooks/common/useMenu' +import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController' +import { useAxios } from '@/hooks/useAxios' +import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting' import { canvasSettingState, canvasState, canvasZoomState, currentMenuState, verticalHorizontalModeState } from '@/store/canvasAtom' import { sessionStore } from '@/store/commonAtom' import { outerLinePointsState } from '@/store/outerLineAtom' import { appMessageStore, globalLocaleStore } from '@/store/localeAtom' -import { settingModalFirstOptionsState } from '@/store/settingAtom' +import { addedRoofsState, basicSettingState, selectedRoofMaterialSelector, settingModalFirstOptionsState } from '@/store/settingAtom' +import { placementShapeDrawingPointsState } from '@/store/placementShapeDrawingAtom' +import { commonUtilsState } from '@/store/commonUtilsAtom' +import { menusState, menuTypeState } from '@/store/menuAtom' +import { estimateState, floorPlanObjectState } from '@/store/floorPlanObjectAtom' +import { pwrGnrSimTypeState } from '@/store/simulatorAtom' +import { isObjectNotEmpty } from '@/util/common-utils' import KO from '@/locales/ko.json' import JA from '@/locales/ja.json' -import { useCanvasEvent } from '@/hooks/useCanvasEvent' -import SettingModal01 from '@/components/floor-plan/modal/setting01/SettingModal01' -import { usePopup } from '@/hooks/usePopup' -import { placementShapeDrawingPointsState } from '@/store/placementShapeDrawingAtom' -import PlacementShapeSetting from '@/components/floor-plan/modal/placementShape/PlacementShapeSetting' -import { useCommonUtils } from '@/hooks/common/useCommonUtils' -import { commonUtilsState } from '@/store/commonUtilsAtom' -import { menusState, menuTypeState } from '@/store/menuAtom' -import useMenu from '@/hooks/common/useMenu' import { MENU } from '@/common/common' -import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController' -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' - -import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting' -import { isObjectNotEmpty } from '@/util/common-utils' - export default function CanvasMenu(props) { const { menuNumber, setMenuNumber } = props const pathname = usePathname() @@ -51,8 +51,8 @@ export default function CanvasMenu(props) { const canvasMenus = useRecoilValue(menusState) const [type, setType] = useRecoilState(menuTypeState) const [verticalHorizontalMode, setVerticalHorizontalMode] = useRecoilState(verticalHorizontalModeState) - const [appMessageState, setAppMessageState] = useRecoilState(appMessageStore) - const [currentMenu, setCurrentMenu] = useRecoilState(currentMenuState) + const setAppMessageState = useSetRecoilState(appMessageStore) + const setCurrentMenu = useSetRecoilState(currentMenuState) const setOuterLinePoints = useSetRecoilState(outerLinePointsState) const setPlacementPoints = useSetRecoilState(placementShapeDrawingPointsState) const canvasSetting = useRecoilValue(canvasSettingState) @@ -75,10 +75,14 @@ export default function CanvasMenu(props) { // 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 { restoreModuleInstArea } = useModuleBasicSetting() + const [addedRoofs, setAddedRoofsState] = useRecoilState(addedRoofsState) + const [basicSetting, setBasicSetting] = useRecoilState(basicSettingState) + const selectedRoofMaterial = useRecoilValue(selectedRoofMaterialSelector) + //견적서버튼 노출용 const [buttonStyle, setButtonStyle] = useState('') @@ -112,6 +116,20 @@ export default function CanvasMenu(props) { if (pathname !== '/floor-plan') router.push('/floor-plan') } + const changeSelectedRoofMaterial = (e) => { + setBasicSetting({ ...basicSetting, selectedRoofMaterial: e }) + + const newAddedRoofs = addedRoofs.map((roof) => { + if (roof.index === e.index) { + return { ...roof, selected: true } + } else { + return { ...roof, selected: false } + } + }) + + setAddedRoofsState(newAddedRoofs) + } + const settingsModalOptions = useRecoilState(settingModalFirstOptionsState) useEffect(() => { @@ -194,7 +212,7 @@ 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) + return (['2', '3'].includes(canvasSetting?.roofSizeSet) && menu.index === 2) || (menuNumber === 4 && menu.index === 2) } // 발전시물레이션 Excel/PDF 다운 @@ -265,7 +283,7 @@ export default function CanvasMenu(props) { key={`canvas-menu-${menu.index}`} className={`canvas-menu-item ${menuNumber === menu.index ? 'active' : ''}`} onClick={() => { - if ([2, 3].some((num) => num === canvasSetting?.roofSizeSet) && menu.index === 2) return + if (['2', '3'].includes(canvasSetting?.roofSizeSet) && menu.index === 2) return if (menuNumber === 4 && menu.index === 2) return onClickNav(menu) }} @@ -292,9 +310,20 @@ export default function CanvasMenu(props) {
-
- -
+ {isObjectNotEmpty(selectedRoofMaterial) && addedRoofs.length > 0 && ( +
+ { + + } +
+ )}
@@ -29,7 +29,7 @@ export default function Eaves({ pitchRef, offsetRef, widthRef, radioTypeRef, pit {getMessage('offset')}
- +
mm diff --git a/src/components/floor-plan/modal/module/PanelEdit.jsx b/src/components/floor-plan/modal/module/PanelEdit.jsx index 1f40b703..5d38f9a8 100644 --- a/src/components/floor-plan/modal/module/PanelEdit.jsx +++ b/src/components/floor-plan/modal/module/PanelEdit.jsx @@ -3,18 +3,71 @@ import { useRecoilValue } from 'recoil' import { contextPopupPositionState } from '@/store/popupAtom' import { usePopup } from '@/hooks/usePopup' import { useMessage } from '@/hooks/useMessage' -import { useState } from 'react' +import { useEffect, useState } from 'react' +import { polygonToTurfPolygon } from '@/util/canvas-util' +import { deepCopyArray } from '@/util/common-utils' +import { canvasState } from '@/store/canvasAtom' +import * as turf from '@turf/turf' +import { POLYGON_TYPE } from '@/common/common' +import { useModal } from '@nextui-org/react' +import { useModule } from '@/hooks/module/useModule' + +export const PANEL_EDIT_TYPE = { + MOVE: 'move', + MOVE_ALL: 'moveAll', + COPY: 'copy', + COPY_ALL: 'copyAll', + COLUMN_MOVE: 'columnMove', + COLUMN_COPY: 'columnCopy', + ROW_MOVE: 'rowMove', + ROW_COPY: 'rowCopy', +} export default function PanelEdit(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) - const { id, pos = contextPopupPosition, type = 'move', apply } = props + const { id, pos = contextPopupPosition, type = PANEL_EDIT_TYPE.MOVE, apply } = props const { closePopup } = usePopup() const [length, setLength] = useState(0) - const [direction, setDirection] = useState('') + const [direction, setDirection] = useState('up') const { getMessage } = useMessage() + const canvas = useRecoilValue(canvasState) + const { moduleMove, moduleCopy, moduleMultiMove, moduleMultiCopy, moduleMoveAll, moduleCopyAll } = useModule() + useEffect(() => { + if (canvas) { + const isSetupModules = canvas.getObjects().filter((obj) => obj.name === 'module') // selectedObj에 없는 객체만 필터링 + isSetupModules.forEach((obj) => obj.set({ lockMovementX: false, lockMovementY: false })) + } + }, []) + + //모듈 이동 적용 const handleApply = () => { - apply() + switch (type) { + case PANEL_EDIT_TYPE.MOVE: + moduleMove(length, direction) + break + case PANEL_EDIT_TYPE.MOVE_ALL: + moduleMoveAll(length, direction) + break + case PANEL_EDIT_TYPE.COPY: + moduleCopy(length, direction) + break + case PANEL_EDIT_TYPE.COPY_ALL: + moduleCopyAll(length, direction) + break + case PANEL_EDIT_TYPE.COLUMN_MOVE: + moduleMultiMove('column', length, direction) + break + case PANEL_EDIT_TYPE.COLUMN_COPY: + moduleMultiCopy('column', length, direction) + break + case PANEL_EDIT_TYPE.ROW_MOVE: + moduleMultiMove('row', length, direction) + break + case PANEL_EDIT_TYPE.ROW_COPY: + moduleMultiCopy('row', length, direction) + break + } closePopup(id) } @@ -22,45 +75,49 @@ export default function PanelEdit(props) {
-

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

+

+ {getMessage([PANEL_EDIT_TYPE.MOVE, PANEL_EDIT_TYPE.COLUMN_MOVE].includes(type) ? 'modal.move.setting' : 'modal.copy.setting')}{' '} +

-
{getMessage(type === 'move' ? 'modal.move.setting.info' : 'modal.copy.setting.info')}
+
+ {getMessage([PANEL_EDIT_TYPE.MOVE, PANEL_EDIT_TYPE.COLUMN_MOVE].includes(type) ? 'modal.move.setting.info' : 'modal.copy.setting.info')} +
{getMessage('margin')}
- setLength(e.target.value)} /> + setLength(e.target.value)} />
mm
diff --git a/src/components/floor-plan/modal/module/column/ColumnInsert.jsx b/src/components/floor-plan/modal/module/column/ColumnInsert.jsx index 8239dc33..56f9a66c 100644 --- a/src/components/floor-plan/modal/module/column/ColumnInsert.jsx +++ b/src/components/floor-plan/modal/module/column/ColumnInsert.jsx @@ -5,20 +5,22 @@ import { usePopup } from '@/hooks/usePopup' import { useMessage } from '@/hooks/useMessage' import { useState } from 'react' import Image from 'next/image' +import { MODULE_INSERT_TYPE, useModule } from '@/hooks/module/useModule' export default function ColumnInsert(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) const { id, pos = contextPopupPosition, apply } = props const { closePopup } = usePopup() - const [selectedType, setSelectedType] = useState(1) + const [selectedType, setSelectedType] = useState(MODULE_INSERT_TYPE.LEFT) const { getMessage } = useMessage() + const { moduleColumnInsert } = useModule() const handleApply = () => { - if (apply) apply() + moduleColumnInsert(selectedType) closePopup(id) } - const HandleRadioChange = (e) => { - setSelectedType(Number(e.target.value)) + const handleRadioChange = (e) => { + setSelectedType(e.target.value) } return ( @@ -36,16 +38,30 @@ export default function ColumnInsert(props) {
- +
- +
- {selectedType === 1 && ( + {selectedType === MODULE_INSERT_TYPE.LEFT && ( react )} - {selectedType === 2 && ( + {selectedType === MODULE_INSERT_TYPE.RIGHT && ( react { - if (apply) apply() + // if (apply) apply() + moduleColumnRemove(selectedType) closePopup(id) } @@ -39,12 +42,12 @@ export default function ColumnRemove(props) {
{types.map((type, index) => { return ( -
+
setSelectedType(Number(e.target.value))} + onClick={(e) => setSelectedType(e.target.value)} value={type.value} checked={selectedType === type.value} /> @@ -54,7 +57,7 @@ export default function ColumnRemove(props) { })}
- {selectedType === 1 && ( + {selectedType === MODULE_REMOVE_TYPE.LEFT && ( react )} - {selectedType === 2 && ( + {selectedType === MODULE_REMOVE_TYPE.RIGHT && ( react )} - {selectedType === 3 && ( + {selectedType === MODULE_REMOVE_TYPE.HORIZONTAL_SIDE && ( react )} - {selectedType === 4 && ( + {selectedType === MODULE_REMOVE_TYPE.NONE && ( react { - if (apply) apply() + muduleRowInsert(selectedType) closePopup(id) } const HandleRadioChange = (e) => { - setSelectedType(Number(e.target.value)) + setSelectedType(e.target.value) } return ( @@ -36,16 +38,30 @@ export default function RowInsert(props) {
- +
- +
- {selectedType === 1 && ( + {selectedType === MODULE_INSERT_TYPE.TOP && ( react )} - {selectedType === 2 && ( + {selectedType === MODULE_INSERT_TYPE.BOTTOM && ( react { - if (apply) apply() + // if (apply) apply() + moduleRowRemove(selectedType) closePopup(id) } @@ -39,22 +42,22 @@ export default function RowRemove(props) {
{types.map((type, index) => { return ( -
+
setSelectedType(Number(e.target.value))} + onClick={(e) => setSelectedType(e.target.value)} value={type.value} checked={selectedType === type.value} /> - +
) })}
- {selectedType === 1 && ( + {selectedType === MODULE_REMOVE_TYPE.TOP && ( react )} - {selectedType === 2 && ( + {selectedType === MODULE_REMOVE_TYPE.BOTTOM && ( react )} - {selectedType === 3 && ( + {selectedType === MODULE_REMOVE_TYPE.VERTICAL_SIDE && ( react )} - {selectedType === 4 && ( + {selectedType === MODULE_REMOVE_TYPE.NONE && ( react { - fetchBasicSettings() + const raftCodeList = findCommonCode('203800') + setRaftCodes(raftCodeList) }, []) useEffect(() => { - console.log(currentRoofMaterial) - }, [roofMaterials]) + setBasicSettings({ + ...basicSetting, + roofsData: { + roofApply: true, + roofSeq: 0, + roofMatlCd: currentRoof.roofMatlCd, + roofWidth: currentRoof.width, + roofHeight: currentRoof.length, + roofHajebichi: currentRoof.hajebichi, + roofGap: currentRoof.raft, + roofLayout: currentRoof.layout, + }, + }) + }, [basicSetting.roofSizeSet, basicSetting.roofAngleSet, currentRoof]) // Function to update the roofType and corresponding values const handleRoofTypeChange = (value) => { const selectedRoofMaterial = roofMaterials.find((roof) => roof.roofMatlCd === value) - setCurrentRoofMaterial(selectedRoofMaterial) - /*const newBasicSetting = { ...basicSetting } - setBasicSettings({ ...newBasicSetting, selectedRoofMaterial: selectedRoofMaterial })*/ + setCurrentRoof({...selectedRoofMaterial, index: 0}) + } + + const changeInput = (value, e) => { + const { name } = e.target + setCurrentRoof({...currentRoof, [name]: Number(value)}) + } + + const handleRafterChange = (value) => { + setCurrentRoof({...currentRoof, raft: value}) + } + + const handleRoofLayoutChange = (value) => { + setCurrentRoof({...currentRoof, layout: value}) + } + + const handleSaveBtn = () => { + const roofInfo = { + ...currentRoof, + roofCd: roofRef.roofCd.current?.value, + width: roofRef.width.current?.value, + length: roofRef.length.current?.value, + hajebichi: roofRef.hajebichi.current?.value, + raft: roofRef.rafter.current?.value, + selected: true, + layout: currentRoof.layout, + index: 0, + } + + const newAddedRoofs = [...addedRoofs] + if (addedRoofs.length === 1) { + newAddedRoofs[0] = { ...roofInfo } + setAddedRoofs(newAddedRoofs) + } + + console.log('save Info', { + ...basicSetting, + selectedRoofMaterial: { + // 선택된 지붕재 정보 + roofInfo, + } + }) + + setBasicSettings({ + ...basicSetting, + selectedRoofMaterial: { + // 선택된 지붕재 정보 + ...roofInfo, + }, + //roofs: addedRoofs, + roofsData: { + roofApply: true, + roofSeq: 0, + roofMatlCd: currentRoof.roofMatlCd, + roofWidth: currentRoof.width, + roofHeight: currentRoof.length, + roofHajebichi: currentRoof.hajebichi, + roofGap: currentRoof.raft, + roofLayout: currentRoof.layout, + }, + }) + + basicSettingSave() } return ( -
+

{getMessage('plan.menu.placement.surface.initial.setting')}

+ +
+
-
diff --git a/src/components/floor-plan/modal/roofAllocation/ContextRoofAllocationSetting.jsx b/src/components/floor-plan/modal/roofAllocation/ContextRoofAllocationSetting.jsx new file mode 100644 index 00000000..23993cb0 --- /dev/null +++ b/src/components/floor-plan/modal/roofAllocation/ContextRoofAllocationSetting.jsx @@ -0,0 +1,199 @@ +import { useMessage } from '@/hooks/useMessage' +import WithDraggable from '@/components/common/draggable/WithDraggable' +import QSelectBox from '@/components/common/select/QSelectBox' +import { useRoofAllocationSetting } from '@/hooks/roofcover/useRoofAllocationSetting' +import { usePopup } from '@/hooks/usePopup' +import { useRecoilState, useRecoilValue } from 'recoil' +import { contextPopupPositionState } from '@/store/popupAtom' +import { useEffect, useState } from 'react' +import { basicSettingState } from '@/store/settingAtom' +import { ROOF_MATERIAL_LAYOUT } from '@/components/floor-plan/modal/placementShape/PlacementShapeSetting' +import { useCanvasSetting } from '@/hooks/option/useCanvasSetting' +import { useCommonCode } from '@/hooks/common/useCommonCode' + +export default function ContextRoofAllocationSetting(props) { + const contextPopupPosition = useRecoilValue(contextPopupPositionState) + const { id, pos = contextPopupPosition } = props + const { getMessage } = useMessage() + const { closePopup } = usePopup() + const { + handleSave, + onAddRoofMaterial, + onDeleteRoofMaterial, + roofMaterials, + setCurrentRoofMaterial, + roofList, + handleDefaultRoofMaterial, + handleChangeRoofMaterial, + handleChangeRaft, + handleChangeLayout, + handleSaveContext, + currentRoofList, + } = useRoofAllocationSetting(id) + + const { findCommonCode } = useCommonCode() + const [raftCodes, setRaftCodes] = useState([]) + useEffect(() => { + const raftCodeList = findCommonCode('203800') + setRaftCodes(raftCodeList.map((raft) => ({ ...raft, value: raft.clCode, name: raft.clCodeNm }))) + }, []) + + return ( + +
+
+

{getMessage('plan.menu.estimate.roof.alloc')}

+ +
+
+
{getMessage('modal.roof.alloc.info')}
+
+ {getMessage('modal.roof.alloc.select.roof.material')} +
+ { + // const selected = roofMaterials.find((roofMaterial) => roofMaterial.roofMatlCd === e.id) + setCurrentRoofMaterial(e) + }} + showKey={'roofMatlNm'} + sourceKey={'roofMatlCd'} + targetKey={'roofMatlCd'} + /> +
+ +
+
+ {currentRoofList.map((roof, index) => { + return ( +
+
+ + +
+
+
+
+
+ handleChangeRoofMaterial(e, index)} + /> +
+ {index === 0 && {getMessage('modal.roof.alloc.default.roof.material')}} + {index !== 0 && } +
+
+ {(roof.widAuth || roof.lenAuth) && ( +
+ {roof.widAuth && ( +
+ W +
+ +
+
+ )} + {roof.lenAuth && ( +
+ L +
+ +
+
+ )} +
+ )} + {(roof.raftAuth || roof.roofPchAuth) && ( +
+ {roof.raftAuth && ( +
+
+ {getMessage('modal.placement.initial.setting.rafter')} + {raftCodes.length > 0 && ( +
+ +
+ )} +
+
+ )} + {roof.roofPchAuth && ( +
+
+ {getMessage('hajebichi')} +
+ +
+
+
+ )} +
+ )} +
+
+ + +
+
+
+
+ ) + })} +
+
+ +
+
+
+
+ ) +} diff --git a/src/components/floor-plan/modal/roofAllocation/RoofAllocationSetting.jsx b/src/components/floor-plan/modal/roofAllocation/RoofAllocationSetting.jsx index f6f1e6f1..d8f7a8ae 100644 --- a/src/components/floor-plan/modal/roofAllocation/RoofAllocationSetting.jsx +++ b/src/components/floor-plan/modal/roofAllocation/RoofAllocationSetting.jsx @@ -5,18 +5,41 @@ import { useRoofAllocationSetting } from '@/hooks/roofcover/useRoofAllocationSet import { usePopup } from '@/hooks/usePopup' import { useRecoilValue } from 'recoil' import { contextPopupPositionState } from '@/store/popupAtom' +import { useEffect, useState } from 'react' +import { ROOF_MATERIAL_LAYOUT } from '@/components/floor-plan/modal/placementShape/PlacementShapeSetting' +import { useCanvasSetting } from '@/hooks/option/useCanvasSetting' +import { useCommonCode } from '@/hooks/common/useCommonCode' +import { globalLocaleStore } from '@/store/localeAtom' export default function RoofAllocationSetting(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) const { id, pos = contextPopupPosition } = props const { getMessage } = useMessage() const { closePopup } = usePopup() - const { handleSave, onAddRoofMaterial, onDeleteRoofMaterial, values, roofMaterials, selectedRoofMaterial, setSelectedRoofMaterial } = - useRoofAllocationSetting(id) + const { + handleSave, + onAddRoofMaterial, + onDeleteRoofMaterial, + roofMaterials, + setCurrentRoofMaterial, + roofList, + handleDefaultRoofMaterial, + handleChangeRoofMaterial, + handleChangeRaft, + handleChangeLayout, + currentRoofList, + } = useRoofAllocationSetting(id) + const { findCommonCode } = useCommonCode() + const [raftCodes, setRaftCodes] = useState([]) + const globalLocale = useRecoilValue(globalLocaleStore) + useEffect(() => { + const raftCodeList = findCommonCode('203800') + setRaftCodes(raftCodeList.map((raft) => ({ ...raft, name: raft.clCodeNm }))) + }, []) return ( -
+

{getMessage('plan.menu.estimate.roof.alloc')}

-
- {values.map((value, index) => ( -
-
- - -
-
-
-
-
- +
+
+ {currentRoofList.map((roof, index) => { + return ( +
+
+ + +
+
+
+
+
+ handleChangeRoofMaterial(e, index)} + /> +
+ {index === 0 && {getMessage('modal.roof.alloc.default.roof.material')}} + {index !== 0 && } +
+
+ {(roof.widAuth || roof.lenAuth) && ( +
+ {roof.widAuth && ( +
+ W +
+ +
+
+ )} + {roof.lenAuth && ( +
+ L +
+ +
+
+ )} +
+ )} + {(roof.raftAuth || roof.roofPchAuth) && ( +
+ {roof.raftAuth && ( +
+
+ {getMessage('modal.placement.initial.setting.rafter')} + {raftCodes.length > 0 && ( +
+ handleChangeRaft(e, index)} + /> +
+ )} +
+
+ )} + {roof.roofPchAuth && ( +
+
+ {getMessage('hajebichi')} +
+ +
+
+
+ )} +
+ )} +
+
+ + +
- {index === 0 && 基本屋根材} - {index !== 0 && }
-
- {value.type === 'A' ? ( - <> -
- W -
- -
-
-
- L -
- -
-
-
- {getMessage('modal.placement.initial.setting.rafter')} -
- -
-
- - ) : value.type === 'B' ? ( - <> -
- {getMessage('hajebichi')} -
- -
-
-
- {getMessage('modal.placement.initial.setting.rafter')} -
- -
-
- - ) : value.type === 'C' ? ( - <> -
- {getMessage('hajebichi')} -
- -
-
- - ) : value.type === 'D' ? ( - <> -
- L -
- -
-
-
- {getMessage('modal.placement.initial.setting.rafter')} -
- -
-
- - ) : ( - '' - )} -
-
-
- - -
-
-
-
- ))} + ) + })} +
- +
+
)} -
- {recentNoticeList.length > 0 ? ( - <> -
{dayjs(recentNoticeList[0]?.regDt).format('YYYY.MM.DD')}
-
{recentNoticeList[0]?.title}
-
{recentNoticeList[0]?.contents}
- - ) : ( - - )} -
+ {recentNoticeList.length > 0 ? ( +
+
{dayjs(recentNoticeList[0]?.regDt).format('YYYY.MM.DD')}
+
{recentNoticeList[0]?.title}
+
') : '' }} + >
+
+ ) : ( +
+

{getMessage('main.content.noBusiness')}

+
+ )}
-
    - {recentFaqList.length > 0 ? ( - <> - {recentFaqList.map((row) => { - return ( -
  • -
    -
    FAQ {row.noticeNo}
    -
    {row.title}
    -
    {dayjs(row.regDt).format('YYYY.MM.DD')}
    -
    -
  • - ) - })} - - ) : ( - - )} -
+ {recentFaqList.length > 0 ? ( +
    + {recentFaqList.map((row) => { + return ( +
  • +
    +
    FAQ {row.totCnt - row.rowNumber}
    +
    {row.title}
    +
    {dayjs(row.regDt).format('YYYY.MM.DD')}
    +
    +
  • + ) + })} +
+ ) : ( +
+

{getMessage('main.content.noBusiness')}

+
+ )}
- -
diff --git a/src/components/main/ProductItem.jsx b/src/components/main/ProductItem.jsx index 707e35c7..c2059c5b 100644 --- a/src/components/main/ProductItem.jsx +++ b/src/components/main/ProductItem.jsx @@ -9,8 +9,10 @@ export default function ProductItem({ num, name, children }) { router.push('/management/stuff', { scroll: false }) } else if (num === 2) { router.push('/community/notice') - } else { + } else if (num === 3) { router.push('/community/faq') + } else { + router.push('/community/archive') } } return ( @@ -20,7 +22,7 @@ export default function ProductItem({ num, name, children }) { {name} - {num !== 4 && num !== 5 && ( + {num !== 5 && ( + ) : ( + + )} + + + +
@@ -1476,7 +1536,7 @@ export default function StuffDetail() { )) || null} - @@ -1685,7 +1745,7 @@ export default function StuffDetail() { onChange={onSelectionChange2} getOptionLabel={(x) => x.saleStoreName} getOptionValue={(x) => x.saleStoreId} - isDisabled={otherSaleStoreList.length > 0 ? false : true} + isDisabled={otherSaleStoreList != null && otherSaleStoreList.length > 0 ? false : true} isClearable={true} value={otherSaleStoreList.filter(function (option) { return option.saleStoreId === otherSelOptions @@ -1713,7 +1773,7 @@ export default function StuffDetail() {
-
{getMessage('stuff.detail.btn.addressPop.guide')}
@@ -1803,7 +1863,7 @@ export default function StuffDetail() { >{getMessage('stuff.detail.standardWindSpeedIdSpan')} - @@ -1925,11 +1985,11 @@ export default function StuffDetail() {
{!isFormValid ? ( - ) : ( - )} @@ -1945,8 +2005,46 @@ export default function StuffDetail() { <>
-
- * {getMessage('stuff.detail.required')} +
+
+ * {getMessage('stuff.detail.required')} +
+ {managementState?.tempFlg === '0' ? ( + <> +
+ + + + + +
+ + ) : ( + <> +
+ {!isFormValid ? ( + + ) : ( + + )} + + + +
+ + )}
@@ -1969,12 +2067,13 @@ export default function StuffDetail() { onClick={() => { form.setValue('planReqNo', '') }} + style={{ display: showButton }} > ) : null}
{managementState?.tempFlg === '1' ? ( <> - @@ -2230,7 +2329,7 @@ export default function StuffDetail() {
-
{getMessage('stuff.detail.btn.addressPop.guide')}
@@ -2325,7 +2424,7 @@ export default function StuffDetail() { >
{getMessage('stuff.detail.standardWindSpeedIdSpan')} -
@@ -2485,7 +2584,7 @@ export default function StuffDetail() {
- +
{/* 진짜R 플랜끝 */} @@ -2495,10 +2594,10 @@ export default function StuffDetail() { {getMessage('stuff.detail.btn.moveList')} - -
@@ -2507,11 +2606,11 @@ export default function StuffDetail() { <>
{!isFormValid ? ( - ) : ( - )} diff --git a/src/components/management/StuffHeader.jsx b/src/components/management/StuffHeader.jsx index f32c971c..76fb6877 100644 --- a/src/components/management/StuffHeader.jsx +++ b/src/components/management/StuffHeader.jsx @@ -3,19 +3,53 @@ import { useContext } from 'react' import { useMessage } from '@/hooks/useMessage' import dayjs from 'dayjs' -import { ManagementContext } from '@/app/management/ManagementProvider' +import { GlobalDataContext } from '@/app/GlobalDataProvider' +// import { ManagementContext } from '@/app/management/ManagementProvider' export default function StuffHeader() { const { getMessage } = useMessage() - const { managementState } = useContext(ManagementContext) + const { managementState } = useContext(GlobalDataContext) //물건번호 복사 + // const copyObjectNo = async (objectNo) => { + // await navigator.clipboard.writeText(objectNo) + // alert(getMessage('stuff.detail.header.successCopy')) + // try { + // } catch (error) { + // alert(getMessage('stuff.detail.header.failCopy')) + // } + // } + const copyObjectNo = async (objectNo) => { - await navigator.clipboard.writeText(objectNo) - alert(getMessage('stuff.detail.header.successCopy')) - try { - } catch (error) { - alert(getMessage('stuff.detail.header.failCopy')) + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard + .writeText(objectNo) + .then(() => { + alert(getMessage('stuff.detail.header.successCopy')) + }) + .catch(() => { + alert(getMessage('stuff.detail.header.failCopy')) + }) + } else { + // Use the 'out of viewport hidden text area' trick + const textArea = document.createElement('textArea') + textArea.value = objectNo + + // Move textarea out of the viewport so it's not visible + textArea.style.position = 'absolute' + textArea.style.left = '-999999px' + + document.body.prepend(textArea) + textArea.select() + + try { + document.execCommand('copy') + alert(getMessage('stuff.detail.header.successCopy')) + } catch (err) { + alert(getMessage('stuff.detail.header.failCopy')) + } finally { + textArea.remove() + } } } diff --git a/src/components/management/StuffPlanQGrid.jsx b/src/components/management/StuffPlanQGrid.jsx index c1e2c605..72c74eb5 100644 --- a/src/components/management/StuffPlanQGrid.jsx +++ b/src/components/management/StuffPlanQGrid.jsx @@ -27,13 +27,10 @@ export default function StuffPlanQGrid(props) { planGridData ? setRowData(planGridData) : '' }, [planGridData]) - // const onGridReady = useCallback( - // (params) => { - // setGridApi(params.api) - // planGridData ? setRowData(planGridData) : '' - // }, - // [planGridData], - // ) + //그리드 더블클릭 추가 + const onCellDoubleClicked = useCallback((params) => { + props.getCellDoubleClicked(params) + }, []) return (
@@ -47,6 +44,7 @@ export default function StuffPlanQGrid(props) { pagination={isPageable} domLayout="autoHeight" suppressCellFocus={true} + onCellDoubleClicked={onCellDoubleClicked} overlayNoRowsTemplate={`${getMessage('stuff.grid.noData')}`} />
diff --git a/src/components/management/StuffSearchCondition.jsx b/src/components/management/StuffSearchCondition.jsx index 111383ed..09fecdb5 100644 --- a/src/components/management/StuffSearchCondition.jsx +++ b/src/components/management/StuffSearchCondition.jsx @@ -86,165 +86,164 @@ export default function StuffSearchCondition() { 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 : '', + schObjectNo: objectNo ? objectNo.trim() : stuffSearch.schObjectNo.trim(), + schSaleStoreName: saleStoreName ? saleStoreName.trim() : '', + schAddress: address ? address.trim() : '', + schObjectName: objectName ? objectName.trim() : '', + schDispCompanyName: dispCompanyName ? dispCompanyName.trim() : '', schSelSaleStoreId: stuffSearch?.schSelSaleStoreId ? stuffSearch.schSelSaleStoreId : '', schOtherSelSaleStoreId: stuffSearch?.schOtherSelSaleStoreId ? stuffSearch.schOtherSelSaleStoreId : '', - schReceiveUser: receiveUser ? receiveUser : '', + schReceiveUser: receiveUser ? receiveUser.trim() : '', 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: stuffSearch?.schSortType ? stuffSearch.schSortType : 'U', pageNo: 1, pageSize: stuffSearch?.pageSize, }) } else { setStuffSearch({ - schObjectNo: objectNo ? objectNo : stuffSearch.schObjectNo, - schSaleStoreName: saleStoreName ? saleStoreName : '', - schAddress: address ? address : '', - schObjectName: objectName ? objectName : '', - schDispCompanyName: dispCompanyName ? dispCompanyName : '', + schObjectNo: objectNo ? objectNo.trim() : stuffSearch.schObjectNo.trim(), + schSaleStoreName: saleStoreName ? saleStoreName.trim() : '', + schAddress: address ? address.trim() : '', + schObjectName: objectName ? objectName.trim() : '', + schDispCompanyName: dispCompanyName ? dispCompanyName.trim() : '', schSelSaleStoreId: stuffSearch?.schSelSaleStoreId ? stuffSearch.schSelSaleStoreId : '', schOtherSelSaleStoreId: stuffSearch?.schOtherSelSaleStoreId ? stuffSearch.schOtherSelSaleStoreId : '', - schReceiveUser: receiveUser ? receiveUser : '', + schReceiveUser: receiveUser ? receiveUser.trim() : '', 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', + schSortType: stuffSearch?.schSortType ? stuffSearch.schSortType : 'U', pageNo: stuffSearch?.pageNo, pageSize: stuffSearch?.pageSize, }) } } else if (stuffSearch.code === 'FINISH') { setStuffSearch({ - schObjectNo: objectNo, - schSaleStoreName: saleStoreName, - schAddress: address, - schObjectName: objectName, - schDispCompanyName: dispCompanyName, + schObjectNo: objectNo.trim(), + schSaleStoreName: saleStoreName.trim(), + schAddress: address.trim(), + schObjectName: objectName.trim(), + schDispCompanyName: dispCompanyName.trim(), schSelSaleStoreId: schSelSaleStoreId, schOtherSelSaleStoreId: otherSaleStoreId, - schReceiveUser: receiveUser, + schReceiveUser: receiveUser.trim(), schDateType: dateType, schFromDt: startDate ? dayjs(startDate).format('YYYY-MM-DD') : '', schToDt: endDate ? dayjs(endDate).format('YYYY-MM-DD') : '', code: 'E', startRow: 1, endRow: 100, - schSortType: stuffSearch?.schSortType ? stuffSearch.schSortType : 'R', + schSortType: stuffSearch?.schSortType ? stuffSearch.schSortType : 'U', }) } 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, + schObjectNo: stuffSearch?.schObjectNo ? stuffSearch.schObjectNo.trim() : objectNo.trim(), + schSaleStoreName: stuffSearch?.schSaleStoreName ? stuffSearch.schSaleStoreName.trim() : saleStoreName.trim(), + schAddress: stuffSearch?.schAddress ? stuffSearch.schAddress.trim() : address.trim(), + schObjectName: stuffSearch?.schObjectName ? stuffSearch.schObjectName.trim() : objectName.trim(), + schDispCompanyName: stuffSearch?.schDispCompanyName ? stuffSearch.schDispCompanyName.trim() : dispCompanyName.trim(), schSelSaleStoreId: otherSaleStoreId ? schSelSaleStoreId : '', schOtherSelSaleStoreId: otherSaleStoreId, - schReceiveUser: stuffSearch?.schReceiveUser ? stuffSearch.schReceiveUser : receiveUser, + schReceiveUser: stuffSearch?.schReceiveUser ? stuffSearch.schReceiveUser.trim() : receiveUser.trim(), 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', + schSortType: stuffSearch?.schSortType ? stuffSearch.schSortType : 'U', 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 : '', + schObjectNo: objectNo ? objectNo.trim() : stuffSearch.schObjectNo.trim(), + schSaleStoreName: saleStoreName ? saleStoreName.trim() : '', + schAddress: address ? address.trim() : '', + schObjectName: objectName ? objectName.trim() : '', + schDispCompanyName: dispCompanyName ? dispCompanyName.trim() : '', schSelSaleStoreId: stuffSearch?.schSelSaleStoreId ? stuffSearch.schSelSaleStoreId : '', schOtherSelSaleStoreId: stuffSearch?.schOtherSelSaleStoreId ? stuffSearch.schOtherSelSaleStoreId : '', - schReceiveUser: receiveUser ? receiveUser : '', + schReceiveUser: receiveUser ? receiveUser.trim() : '', 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', + schSortType: 'U', 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, + schObjectNo: stuffSearch?.schObjectNo ? stuffSearch.schObjectNo.trim() : objectNo.trim(), + schSaleStoreName: stuffSearch?.schSaleStoreName ? stuffSearch.schSaleStoreName.trim() : saleStoreName.trim(), + schAddress: stuffSearch?.schAddress ? stuffSearch.schAddress.trim() : address.trim(), + schObjectName: stuffSearch?.schObjectName ? stuffSearch.schObjectName.trim() : objectName.trim(), + schDispCompanyName: stuffSearch?.schDispCompanyName ? stuffSearch.schDispCompanyName.trim() : dispCompanyName.trim(), schSelSaleStoreId: schSelSaleStoreId, schOtherSelSaleStoreId: otherSaleStoreId, - schReceiveUser: stuffSearch?.schReceiveUser ? stuffSearch.schReceiveUser : receiveUser, + schReceiveUser: stuffSearch?.schReceiveUser ? stuffSearch.schReceiveUser.trim() : receiveUser.trim(), 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', + schSortType: stuffSearch?.schSortType ? stuffSearch.schSortType : 'U', 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, + schObjectNo: stuffSearch?.schObjectNo ? stuffSearch.schObjectNo.trim() : objectNo.trim(), + schSaleStoreName: stuffSearch?.schSaleStoreName ? stuffSearch.schSaleStoreName.trim() : saleStoreName.trim(), + schAddress: stuffSearch?.schAddress ? stuffSearch.schAddress.trim() : address.trim(), + schObjectName: stuffSearch?.schObjectName ? stuffSearch.schObjectName.trim() : objectName.trim(), + schDispCompanyName: stuffSearch?.schDispCompanyName ? stuffSearch.schDispCompanyName.trim() : dispCompanyName.trim(), schSelSaleStoreId: schSelSaleStoreId, schOtherSelSaleStoreId: otherSaleStoreId, - schReceiveUser: stuffSearch?.schReceiveUser ? stuffSearch.schReceiveUser : receiveUser, + schReceiveUser: stuffSearch?.schReceiveUser ? stuffSearch.schReceiveUser.trim() : receiveUser.trim(), 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', + schSortType: stuffSearch?.schSortType ? stuffSearch.schSortType : 'U', pageNo: stuffSearch?.pageNo, pageSize: stuffSearch?.pageSize, }) } } else { setStuffSearch({ - schObjectNo: objectNo, - schSaleStoreName: saleStoreName, - schAddress: address, - schObjectName: objectName, - schDispCompanyName: dispCompanyName, + schObjectNo: objectNo.trim(), + schSaleStoreName: saleStoreName.trim(), + schAddress: address.trim(), + schObjectName: objectName.trim(), + schDispCompanyName: dispCompanyName.trim(), schSelSaleStoreId: schSelSaleStoreId, schOtherSelSaleStoreId: otherSaleStoreId, - schReceiveUser: receiveUser, + schReceiveUser: receiveUser.trim(), 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', + schSortType: stuffSearch?.schSortType ? stuffSearch.schSortType : 'U', pageNo: stuffSearch?.pageNo, pageSize: stuffSearch?.pageSize, }) @@ -288,10 +287,9 @@ export default function StuffSearchCondition() { schDateType: 'U', startRow: 1, endRow: 100, - schSortType: 'R', + schSortType: 'U', pageNo: 1, pageSize: 100, - // code: 'S', }) } else { if (otherSaleStoreList.length > 1) { @@ -307,7 +305,7 @@ export default function StuffSearchCondition() { stuffSearch.startRow = 1 stuffSearch.endRow = 100 - stuffSearch.schSortType = 'R' + stuffSearch.schSortType = 'U' stuffSearch.pageNo = 1 stuffSearch.pageSize = 100 } else { @@ -321,7 +319,7 @@ export default function StuffSearchCondition() { stuffSearch.startRow = 1 stuffSearch.endRow = 100 - stuffSearch.schSortType = 'R' + stuffSearch.schSortType = 'U' stuffSearch.pageNo = 1 stuffSearch.pageSize = 100 } @@ -347,6 +345,8 @@ export default function StuffSearchCondition() { get({ url: url }).then((res) => { if (!isEmptyArray(res)) { res.map((row) => { + //#399 + row.saleStoreName = row.saleStoreName + ' - ' + row.saleStoreId row.value = row.saleStoreId row.label = row.saleStoreName }) @@ -367,6 +367,8 @@ export default function StuffSearchCondition() { get({ url: url }).then((res) => { if (!isEmptyArray(res)) { res.map((row) => { + //#399 + row.saleStoreName = row.saleStoreName + ' - ' + row.saleStoreId row.value = row.saleStoreId row.label = row.saleStoreName }) @@ -456,6 +458,8 @@ export default function StuffSearchCondition() { get({ url: url }).then((res) => { if (!isEmptyArray(res)) { res.map((row) => { + //#399 + row.saleStoreName = row.saleStoreName + ' - ' + row.saleStoreId row.value = row.saleStoreId row.label = row.saleStoreName }) @@ -474,6 +478,10 @@ export default function StuffSearchCondition() { if (stuffSearch.code === 'S') { stuffSearch.schSelSaleStoreId = '' stuffSearch.schOtherSelSaleStoreId = '' + } else if (stuffSearch.code === 'E') { + //#401 + stuffSearch.schSelSaleStoreId = '' + stuffSearch.schOtherSelSaleStoreId = '' } //2차점 판매점목록비우기 @@ -540,7 +548,7 @@ export default function StuffSearchCondition() { stuffSearch.endRow = 100 stuffSearch.schSelSaleStoreId = '' stuffSearch.schOtherSelSaleStoreId = '' - stuffSearch.schSortType = 'R' + stuffSearch.schSortType = 'U' stuffSearch.pageNo = 1 stuffSearch.pageSize = 100 @@ -572,7 +580,7 @@ export default function StuffSearchCondition() { stuffSearch.endRow = 100 stuffSearch.schSelSaleStoreId = '' stuffSearch.schOtherSelSaleStoreId = '' - stuffSearch.schSortType = 'R' + stuffSearch.schSortType = 'U' stuffSearch.pageNo = 1 stuffSearch.pageSize = 100 setSchSelSaleStoreId('') diff --git a/src/components/management/StuffSubHeader.jsx b/src/components/management/StuffSubHeader.jsx index 27acf615..c92b4616 100644 --- a/src/components/management/StuffSubHeader.jsx +++ b/src/components/management/StuffSubHeader.jsx @@ -13,7 +13,8 @@ import { useMessage } from '@/hooks/useMessage' import { floorPlanObjectState } from '@/store/floorPlanObjectAtom' import { isObjectNotEmpty, queryStringFormatter } from '@/util/common-utils' -import { ManagementContext } from '@/app/management/ManagementProvider' +import { GlobalDataContext } from '@/app/GlobalDataProvider' +// import { ManagementContext } from '@/app/management/ManagementProvider' import { SessionContext } from '@/app/SessionProvider' export default function StuffSubHeader({ type }) { @@ -25,7 +26,7 @@ export default function StuffSubHeader({ type }) { const { isGlobalLoading } = useContext(QcastContext) - const { managementState } = useContext(ManagementContext) + const { managementState } = useContext(GlobalDataContext) const [buttonStyle, setButtonStyle] = useState('') useEffect(() => { diff --git a/src/components/management/popup/PlanRequestPop.jsx b/src/components/management/popup/PlanRequestPop.jsx index c7a3f7cd..aa415edd 100644 --- a/src/components/management/popup/PlanRequestPop.jsx +++ b/src/components/management/popup/PlanRequestPop.jsx @@ -87,7 +87,7 @@ export default function PlanRequestPop(props) { const onSubmit = (page, type) => { //2차점 테스트 201X112 const params = { - // saleStoreId: 'T100', + // saleStoreId: 'X112', // saleStoreLevel: '1', saleStoreId: props?.otherSaleStoreId ? props.otherSaleStoreId : props.saleStoreId, saleStoreLevel: props?.otherSaleStoreLevel ? props.otherSaleStoreLevel : props.saleStoreLevel, diff --git a/src/components/sample/SampleReducer.jsx b/src/components/sample/SampleReducer.jsx new file mode 100644 index 00000000..c7403c25 --- /dev/null +++ b/src/components/sample/SampleReducer.jsx @@ -0,0 +1,115 @@ +import { Card, CardBody, Input, Tab, Tabs } from '@nextui-org/react' +import { useEffect, useReducer } from 'react' + +const reducer = (prevState, nextState) => { + return { ...prevState, ...nextState } +} + +const defaultData = { + commonData: 'common', + tabs: [ + { + id: 1, + name: 'tab1', + range: 10, + maker: 'maker1', + law: 'law1', + basis: 'basis1', + }, + { + id: 2, + name: 'tab2', + range: 20, + maker: 'maker2', + law: 'law2', + basis: 'basis2', + }, + { + id: 3, + name: 'tab3', + range: 30, + maker: 'maker3', + law: 'law3', + basis: 'basis3', + }, + { + id: 4, + name: 'tab4', + range: 40, + maker: 'maker4', + law: 'law4', + basis: 'basis4', + }, + ], +} + +export default function SampleReducer() { + const [sampleState, setSampleState] = useReducer(reducer, defaultData) + + const handleChangeTabsData = (newTab) => { + const newTabs = sampleState.tabs.map((t) => { + if (t.id === newTab.id) { + return newTab + } else { + return t + } + }) + setSampleState({ tabs: newTabs }) + } + + useEffect(() => { + console.log('🚀 ~ SampleReducer ~ sampleState:', sampleState) + }, [sampleState]) + + return ( + <> +
공통: {sampleState.commonData}
+
+ + {sampleState.tabs.map((s) => ( + + + +
+ { + handleChangeTabsData({ ...s, range: e.target.value }) + }} + /> + { + handleChangeTabsData({ ...s, maker: e.target.value }) + }} + /> + { + handleChangeTabsData({ ...s, law: e.target.value }) + }} + /> + { + handleChangeTabsData({ ...s, basis: e.target.value }) + }} + /> +
+
+
+
+ ))} +
+
+ + ) +} diff --git a/src/hooks/common/useCanvasConfigInitialize.js b/src/hooks/common/useCanvasConfigInitialize.js index 18d1a052..c178bf75 100644 --- a/src/hooks/common/useCanvasConfigInitialize.js +++ b/src/hooks/common/useCanvasConfigInitialize.js @@ -2,12 +2,13 @@ import { useEffect } from 'react' import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' import { basicSettingState, roofDisplaySelector, settingModalFirstOptionsState } from '@/store/settingAtom' import { canvasState, dotLineGridSettingState, pitchText, pitchTextSelector, showAngleUnitSelector } from '@/store/canvasAtom' -import { getChonByDegree, getDegreeByChon, setSurfaceShapePattern } from '@/util/canvas-util' +import { getChonByDegree, getDegreeByChon } from '@/util/canvas-util' import { useFont } from '@/hooks/common/useFont' import { useGrid } from '@/hooks/common/useGrid' import { globalFontAtom } from '@/store/fontAtom' import { useRoof } from '@/hooks/common/useRoof' import { usePolygon } from '@/hooks/usePolygon' +import { useRoofFn } from '@/hooks/common/useRoofFn' export function useCanvasConfigInitialize() { const canvas = useRecoilValue(canvasState) @@ -18,6 +19,7 @@ export function useCanvasConfigInitialize() { const setDotLineGridSetting = useSetRecoilState(dotLineGridSettingState) const pitchText = useRecoilValue(pitchTextSelector) const angleUnit = useRecoilValue(showAngleUnitSelector) + const { setSurfaceShapePattern } = useRoofFn() const {} = useFont() const {} = useGrid() const {} = useRoof() @@ -63,6 +65,7 @@ export function useCanvasConfigInitialize() { roofInit() //화면표시 초기화 groupDimensionInit() reGroupInit() //그룹 객체 재그룹 + moduleInit() } const gridInit = () => { @@ -194,5 +197,19 @@ export function useCanvasConfigInitialize() { }) } + const moduleInit = () => { + canvas + .getObjects() + .filter((obj) => obj.name === 'module') + .forEach((obj) => { + obj.set({ + selectable: true, + lockMovementX: false, + lockMovementY: false, + }) + obj.setViewLengthText(false) + }) + } + return { canvasLoadInit, gridInit } } diff --git a/src/hooks/common/useCommonCode.js b/src/hooks/common/useCommonCode.js index bab32007..fc902ef2 100644 --- a/src/hooks/common/useCommonCode.js +++ b/src/hooks/common/useCommonCode.js @@ -39,10 +39,6 @@ export const useCommonCode = () => { return resultCodes } - useEffect(() => { - findCommonCode() - }, [globalLocale]) - useEffect(() => { const getCommonCode = async () => { await promiseGet({ url: '/api/commcode/qc-comm-code' }).then((res) => { diff --git a/src/hooks/common/useCommonUtils.js b/src/hooks/common/useCommonUtils.js index 1f9c2063..a299fd75 100644 --- a/src/hooks/common/useCommonUtils.js +++ b/src/hooks/common/useCommonUtils.js @@ -603,8 +603,12 @@ export function useCommonUtils() { } const deleteObject = () => { - const obj = canvas?.getActiveObject() - commonDeleteText(obj) + const selectedObj = canvas?.getActiveObjects() + if (selectedObj) { + selectedObj.forEach((obj) => { + commonDeleteText(obj) + }) + } } const moveObject = () => { diff --git a/src/hooks/common/useGrid.js b/src/hooks/common/useGrid.js index 5565ef54..2b6e9ba9 100644 --- a/src/hooks/common/useGrid.js +++ b/src/hooks/common/useGrid.js @@ -28,11 +28,7 @@ export function useGrid() { // 1. 점.선 그리드 설정으로 만들어진 기존 오브젝트 제거 canvas ?.getObjects() - .filter((obj) => obj.name === 'lineGrid') - .forEach((obj) => canvas?.remove(obj)) - canvas - ?.getObjects() - .filter((obj) => obj.name === 'dotGrid') + .filter((obj) => ['lineGrid', 'dotGrid'].includes(obj.name)) .forEach((obj) => canvas?.remove(obj)) //const horizontalInterval = interval.horizontalInterval @@ -181,8 +177,17 @@ export function useGrid() { canvas.renderAll() } + const removeGrid = () => { + canvas + .getObjects() + .filter((obj) => ['lineGrid', 'dotGrid', 'tempGrid'].includes(obj.name)) + .forEach((obj) => canvas.remove(obj)) + canvas.renderAll() + } + return { move, copy, + removeGrid, } } diff --git a/src/hooks/common/useMasterController.js b/src/hooks/common/useMasterController.js index 4f8bb799..6f7a8fb9 100644 --- a/src/hooks/common/useMasterController.js +++ b/src/hooks/common/useMasterController.js @@ -2,7 +2,7 @@ import { useAxios } from '@/hooks/useAxios' import { useMessage } from '@/hooks/useMessage' import { useSwal } from '@/hooks/useSwal' import { getQueryString } from '@/util/common-utils' -import axios from 'axios' +import { trestleRequest, constructionRequest, trestleDetailRequest } from '@/models/apiModels' /** * 마스터 컨트롤러 훅 @@ -26,16 +26,15 @@ export function useMasterController() { /** * 모듈 타입별 아이템 목록 조회 - * @param {지붕재 코드} roofMatlCd + * @param {지붕재 코드 목록} arrRoofMatlCd * @returns */ - const getModuleTypeItemList = async (roofMatlCd) => { - if (!roofMatlCd || roofMatlCd.trim() === '') { + const getModuleTypeItemList = async (paramArr) => { + if (!Array.isArray(paramArr) || paramArr.length === 0 || paramArr.length > 4 || paramArr.some((item) => !item || item.trim() === '')) { swalFire({ text: getMessage('master.moduletypeitem.message.error'), type: 'alert', icon: 'error' }) return null } - const param = { roofMatlCd: roofMatlCd } - const paramString = getQueryString(param) + const paramString = `?${paramArr.map((item) => `arrRoofMatlCd=${item}`).join('&')}` return await get({ url: `/api/v1/master/getModuleTypeItemList${paramString}` }).then((res) => { console.log('🚀🚀 ~ getModuleTypeItemList ~ res:', res) return res @@ -44,11 +43,18 @@ export function useMasterController() { /** * 가대 목록 조회 - * @param + * @param {모듈타입코드} moduleTpCd + * @param {지붕재코드} roofMatlCd + * @param {서까래기초코드} raftBaseCd + * @param {가대메이커코드} trestleMkrCd + * @param {공법코드} constMthdCd + * @param {지붕기초코드} roofBaseCd * @returns */ const getTrestleList = async (params) => { - return await get({ url: `/api/v1/master/getTrestleList/${params}` }).then((res) => { + const paramString = getQueryString(params) + console.log('🚀🚀 ~ getTrestleList ~ paramString:', paramString) + return await get({ url: '/api/v1/master/getTrestleList' + paramString }).then((res) => { console.log('🚀🚀 ~ getTrestleList ~ res:', res) return res }) @@ -56,11 +62,25 @@ export function useMasterController() { /** * 모듈 시공법 목록 조회 - * @param + * @param {모듈타입코드} moduleTpCd + * @param {지붕재코드} roofMatlCd + * @param {가대메이커코드} trestleMkrCd + * @param {공법코드} constMthdCd + * @param {지붕기초코드} roofBaseCd + * @param {면조도} illuminationTp + * @param {설치높이} instHt + * @param {풍속} stdWindSpeed + * @param {적설량} stdSnowLd + * @param {경사도코드} inclCd + * @param {서까래기초코드} raftBaseCd + * @param {하제(망둥어)피치} roofPitch + * * @returns */ const getConstructionList = async (params) => { - return await get({ url: `/api/v1/master/getConstructionList/${params}` }).then((res) => { + const paramString = getQueryString(params) + console.log('🚀🚀 ~ getConstructionList ~ paramString:', paramString) + return await get({ url: '/api/v1/master/getConstructionList' + paramString }).then((res) => { console.log('🚀🚀 ~ getConstructionList ~ res:', res) return res }) @@ -68,11 +88,25 @@ export function useMasterController() { /** * 가대 상세 조회 - * @param + * @param {모듈타입코드} moduleTpCd + * @param {지붕재코드} roofMatlCd + * @param {가대메이커코드} trestleMkrCd + * @param {공법코드} constMthdCd + * @param {지붕기초코드} roofBaseCd + * @param {면조도} illuminationTp + * @param {설치높이} instHt + * @param {풍속} stdWindSpeed + * @param {적설량} stdSnowLd + * @param {경사도코드} inclCd + * @param {시공법} constTp + * @param {혼합모듈번호} mixMatlNo + * @param {하제(망둥어)피치}roofPitch * @returns */ const getTrestleDetailList = async (params) => { - return await get({ url: `/api/v1/master/getTrestleDetailList/${params}` }).then((res) => { + const paramString = getQueryString(params) + console.log('🚀🚀 ~ getTrestleDetailList ~ paramString:', paramString) + return await get({ url: '/api/v1/master/getTrestleDetailList' + paramString }).then((res) => { console.log('🚀🚀 ~ getTrestleDetailList ~ res:', res) return res }) diff --git a/src/hooks/common/useRoof.js b/src/hooks/common/useRoof.js index 8aee0344..0b5e5a6b 100644 --- a/src/hooks/common/useRoof.js +++ b/src/hooks/common/useRoof.js @@ -2,12 +2,13 @@ import { canvasState } from '@/store/canvasAtom' import { allocDisplaySelector, roofDisplaySelector } from '@/store/settingAtom' import { useRecoilValue } from 'recoil' import { useEffect } from 'react' +import { useRoofFn } from '@/hooks/common/useRoofFn' export function useRoof() { const canvas = useRecoilValue(canvasState) const allocDisplay = useRecoilValue(allocDisplaySelector) const roofDisplay = useRecoilValue(roofDisplaySelector) - + const { setSurfaceShapePattern } = useRoofFn() useEffect(() => { if (!canvas) return canvas @@ -23,7 +24,7 @@ export function useRoof() { canvas.renderAll() }, [allocDisplay]) - const setSurfaceShapePattern = (polygon, mode = 'onlyBorder') => { + /*const setSurfaceShapePattern = (polygon, mode = 'onlyBorder') => { const ratio = window.devicePixelRatio || 1 let width = 265 / 10 @@ -145,7 +146,7 @@ export function useRoof() { polygon.set('fill', null) polygon.set('fill', pattern) polygon.canvas?.renderAll() - } + }*/ return {} } diff --git a/src/hooks/common/useRoofFn.js b/src/hooks/common/useRoofFn.js new file mode 100644 index 00000000..ccb0d173 --- /dev/null +++ b/src/hooks/common/useRoofFn.js @@ -0,0 +1,170 @@ +import { useRecoilValue } from 'recoil' +import { canvasState, currentObjectState } from '@/store/canvasAtom' +import { selectedRoofMaterialSelector } from '@/store/settingAtom' +import { ROOF_MATERIAL_LAYOUT } from '@/components/floor-plan/modal/placementShape/PlacementShapeSetting' +import { POLYGON_TYPE } from '@/common/common' +const ROOF_COLOR = { + 0: 'rgb(199,240,213)', + 1: 'rgb(178,238,255)', + 2: 'rgb(187,204,255)', + 3: 'rgb(228,202,255)', +} +export function useRoofFn() { + const canvas = useRecoilValue(canvasState) + const selectedRoofMaterial = useRecoilValue(selectedRoofMaterialSelector) + const currentObject = useRecoilValue(currentObjectState) + + //면형상 선택 클릭시 지붕 패턴 입히기 + function setSurfaceShapePattern(polygon, mode = 'onlyBorder', trestleMode = false, roofMaterial = selectedRoofMaterial) { + const ratio = window.devicePixelRatio || 1 + const layout = roofMaterial.layout + + let width = (roofMaterial.width ?? 226) / 10 + let height = (roofMaterial.length ?? 158) / 10 + const index = roofMaterial.index ?? 0 + let roofStyle = 2 + const inputPatternSize = { width: width, height: height } //임시 사이즈 + const patternSize = { ...inputPatternSize } // 입력된 값을 뒤집기 위해 + + if (polygon.direction === 'east' || polygon.direction === 'west') { + //세로형이면 width height를 바꿈 + ;[patternSize.width, patternSize.height] = [inputPatternSize.height, patternSize.width] + } + + // 패턴 소스를 위한 임시 캔버스 생성 + const patternSourceCanvas = document.createElement('canvas') + patternSourceCanvas.width = polygon.width * ratio + patternSourceCanvas.height = polygon.height * ratio + const ctx = patternSourceCanvas.getContext('2d') + let offset = roofStyle === 1 ? 0 : patternSize.width / 2 + + const rows = Math.floor(patternSourceCanvas.height / patternSize.height) + const cols = Math.floor(patternSourceCanvas.width / patternSize.width) + + ctx.strokeStyle = mode === 'allPainted' ? 'black' : ROOF_COLOR[index] + ctx.lineWidth = 2 + 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)' + } else { + ctx.fillStyle = 'rgba(255, 255, 255, 1)' + } + + if (polygon.direction === 'east' || polygon.direction === 'west') { + offset = roofStyle === 1 ? 0 : patternSize.height / 2 + for (let col = 0; col <= cols; col++) { + const x = col * patternSize.width + const yStart = 0 + const yEnd = patternSourceCanvas.height + ctx.beginPath() + ctx.moveTo(x, yStart) // 선 시작점 + ctx.lineTo(x, yEnd) // 선 끝점 + ctx.stroke() + if (mode === 'allPainted' || trestleMode) { + ctx.fillRect(x, yStart, patternSize.width, yEnd - yStart) + } + + for (let row = 0; row <= rows; row++) { + const y = layout === ROOF_MATERIAL_LAYOUT.STAIRS ? row * patternSize.height + (col % 2 === 0 ? 0 : offset) : row * patternSize.height + const xStart = col * patternSize.width + const xEnd = xStart + patternSize.width + ctx.beginPath() + ctx.moveTo(xStart, y) // 선 시작점 + ctx.lineTo(xEnd, y) // 선 끝점 + ctx.stroke() + if (mode === 'allPainted' || trestleMode) { + ctx.fillRect(xStart, y, xEnd - xStart, patternSize.height) + } + } + } + } else { + for (let row = 0; row <= rows; row++) { + const y = row * patternSize.height + + ctx.beginPath() + ctx.moveTo(0, y) // 선 시작점 + ctx.lineTo(patternSourceCanvas.width, y) // 선 끝점 + ctx.stroke() + if (mode === 'allPainted' || trestleMode) { + ctx.fillRect(0, y, patternSourceCanvas.width, patternSize.height) + } + + for (let col = 0; col <= cols; col++) { + const x = layout === ROOF_MATERIAL_LAYOUT.STAIRS ? col * patternSize.width + (row % 2 === 0 ? 0 : offset) : col * patternSize.width + const yStart = row * patternSize.height + const yEnd = yStart + patternSize.height + + ctx.beginPath() + ctx.moveTo(x, yStart) // 선 시작점 + ctx.lineTo(x, yEnd) // 선 끝점 + ctx.stroke() + if (mode === 'allPainted' || trestleMode) { + ctx.fillRect(x, yStart, patternSize.width, yEnd - yStart) + } + } + } + } + + const hachingPatternSourceCanvas = document.createElement('canvas') + + if (mode === 'lineHatch') { + hachingPatternSourceCanvas.width = polygon.width * ratio + hachingPatternSourceCanvas.height = polygon.height * ratio + + const ctx1 = hachingPatternSourceCanvas.getContext('2d') + + const gap = 10 + + ctx1.strokeStyle = 'green' // 선 색상 + ctx1.lineWidth = 0.3 // 선 두께 + + for (let x = 0; x < hachingPatternSourceCanvas.width + hachingPatternSourceCanvas.height; x += gap) { + ctx1.beginPath() + ctx1.moveTo(x, 0) // 선 시작점 + ctx1.lineTo(0, x) // 선 끝점 + ctx1.stroke() + } + } + + const combinedPatternCanvas = document.createElement('canvas') + combinedPatternCanvas.width = polygon.width * ratio + combinedPatternCanvas.height = polygon.height * ratio + const combinedCtx = combinedPatternCanvas.getContext('2d') + + // 첫 번째 패턴을 그린 후 두 번째 패턴을 덧입힘 + combinedCtx.drawImage(patternSourceCanvas, 0, 0) + combinedCtx.drawImage(hachingPatternSourceCanvas, 0, 0) + + // 패턴 생성 + const pattern = new fabric.Pattern({ + source: combinedPatternCanvas, + repeat: 'repeat', + }) + + polygon.set('fill', null) + polygon.set('fill', pattern) + polygon.roofMaterial = roofMaterial + polygon.canvas?.renderAll() + } + + function removeRoofMaterial(roof = currentObject) { + if (roof === null || roof.name !== POLYGON_TYPE.ROOF) { + return + } + roof.set('fill', null) + roof.roofMaterial = null + canvas?.renderAll() + } + + function removeAllRoofMaterial() { + const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) + roofBases.forEach((roofBase) => { + removeRoofMaterial(roofBase) + }) + } + + return { setSurfaceShapePattern, removeRoofMaterial, removeAllRoofMaterial } +} diff --git a/src/hooks/module/useModule.js b/src/hooks/module/useModule.js new file mode 100644 index 00000000..a71744c6 --- /dev/null +++ b/src/hooks/module/useModule.js @@ -0,0 +1,940 @@ +import { BATCH_TYPE, POLYGON_TYPE } from '@/common/common' +import { canvasState } from '@/store/canvasAtom' +import { isOverlap, polygonToTurfPolygon, rectToPolygon } from '@/util/canvas-util' +import { useRecoilValue } from 'recoil' +import { v4 as uuidv4 } from 'uuid' +import * as turf from '@turf/turf' +import { useSwal } from '../useSwal' +import { useModuleBasicSetting } from './useModuleBasicSetting' +import { useMessage } from '../useMessage' + +export const MODULE_REMOVE_TYPE = { + LEFT: 'left', + RIGHT: 'right', + HORIZONTAL_SIDE: 'horizontalSide', + TOP: 'top', + BOTTOM: 'bottom', + VERTICAL_SIDE: 'verticalSide', + NONE: 'none', +} + +export const MODULE_INSERT_TYPE = { + LEFT: 'left', + RIGHT: 'right', + TOP: 'up', + BOTTOM: 'down', +} + +export const MODULE_ALIGN_TYPE = { + VERTICAL: 'vertical', + HORIZONTAL: 'horizontal', +} + +export function useModule() { + const canvas = useRecoilValue(canvasState) + const { swalFire } = useSwal() + const { getMessage } = useMessage() + const { checkModuleDisjointObjects } = useModuleBasicSetting() + + const moduleMove = (length, direction) => { + const selectedObj = canvas.getActiveObjects() //선택된 객체들을 가져옴 + const selectedIds = selectedObj.map((obj) => obj.id) // selectedObj의 ID 추출 + + canvas.discardActiveObject() //선택해제 + + const isSetupModules = getOtherModules(selectedObj) + const selectedModules = canvas.getObjects().filter((obj) => selectedIds.includes(obj.id) && obj.name === 'module') //선택했던 객체들만 가져옴 + const setupSurface = canvas + .getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && obj.id === selectedModules[0].surfaceId)[0] + let isWarning = false + const objects = getObjects() + + if (selectedModules) { + selectedModules.forEach((module) => { + const { top, left } = getPosotion(module, direction, length, false) + module.originCoords = { + left: module.left, + top: module.top, + fill: module.fill, + } + module.set({ top, left }) + module.setCoords() + + if (isOverlapOtherModules(module, isSetupModules) || isOverlapObjects(module, objects) || isOutsideSurface(module, setupSurface)) { + isWarning = true + module.set({ fill: 'red' }) + } + canvas.renderAll() + }) + + if (isWarning) { + swalFire({ + title: getMessage('can.not.move.module'), + icon: 'error', + type: 'alert', + confirmFn: () => { + selectedModules.forEach((module) => { + module.set({ left: module.originCoords.left, top: module.originCoords.top, fill: module.originCoords.fill }) + module.setCoords() + }) + canvas.renderAll() + }, + }) + } + } + } + const moduleMultiMove = (type, length, direction) => { + if (canvas.getActiveObjects().length === 0) return + if (canvas.getActiveObjects().length > 1) { + swalFire({ + title: '여러 개의 모듈을 선택할 수 없습니다.', + icon: 'error', + type: 'alert', + }) + canvas.discardActiveObject() + return + } + const activeModule = canvas.getObjects().filter((obj) => canvas.getActiveObjects()[0].id === obj.id)[0] + const modules = type === 'row' ? getRowModules(activeModule) : getColumnModules(activeModule) + const otherModules = getOtherModules(modules) + const objects = getObjects() + const moduleSetupSurface = canvas + .getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && obj.id === activeModule.surfaceId)[0] + let isWarning = false + + modules.forEach((module) => { + const { top, left } = getPosotion(module, direction, length, false) + module.originPos = { + top: module.top, + left: module.left, + fill: module.fill, + } + + module.set({ top, left }) + module.setCoords() + canvas.renderAll() + + if (otherModules.length > 0) { + if (isOverlapOtherModules(module, otherModules) || isOverlapObjects(module, objects) || isOutsideSurface(module, moduleSetupSurface)) { + isWarning = true + module.set({ fill: 'red' }) + } + } + }) + + canvas.renderAll() + if (isWarning) { + swalFire({ + title: getMessage('can.not.move.module'), + icon: 'error', + type: 'alert', + confirmFn: () => { + modules.forEach((module) => { + module.set({ top: module.originPos.top, left: module.originPos.left, fill: module.originPos.fill }) + module.setCoords() + }) + canvas.renderAll() + }, + }) + } + } + + const moduleMoveAll = (length, direction) => { + const moduleSetupSurface = canvas.getObjects().filter((obj) => canvas.getActiveObjects()[0].id === obj.id)[0] + const modules = canvas.getObjects().filter((obj) => obj.surfaceId === moduleSetupSurface.id && obj.name === POLYGON_TYPE.MODULE) + const objects = getObjects() + + let isWarning = false + + modules.forEach((module) => { + const { top, left } = getPosotion(module, direction, length, false) + module.originPos = { + top: module.top, + left: module.left, + fill: module.fill, + } + + module.set({ top, left }) + module.setCoords() + canvas.renderAll() + + if (isOverlapObjects(module, objects) || isOutsideSurface(module, moduleSetupSurface)) { + isWarning = true + module.set({ fill: 'red' }) + } + }) + + canvas.renderAll() + if (isWarning) { + swalFire({ + title: getMessage('can.not.move.module'), + icon: 'error', + type: 'alert', + confirmFn: () => { + modules.forEach((module) => { + module.set({ top: module.originPos.top, left: module.originPos.left, fill: module.originPos.fill }) + module.setCoords() + }) + canvas.renderAll() + }, + }) + } + } + + const moduleCopyAll = (length, direction) => { + const moduleSetupSurface = canvas.getObjects().filter((obj) => canvas.getActiveObjects()[0].id === obj.id)[0] + const modules = canvas.getObjects().filter((obj) => obj.surfaceId === moduleSetupSurface.id && obj.name === POLYGON_TYPE.MODULE) + const objects = getObjects() + const copyModules = [] + let copyModule = null + let isWarning = false + let moduleLength = 0 + if (['up', 'down'].includes(direction)) { + modules.sort((a, b) => a.top - b.top) + moduleLength = Number(modules[modules.length - 1].top) + Number(modules[modules.length - 1].height) - Number(modules[0].top) + } else if (['left', 'right'].includes(direction)) { + modules.sort((a, b) => a.left - b.left) + moduleLength = Number(modules[modules.length - 1].left) + Number(modules[modules.length - 1].width) - Number(modules[0].left) + } + + modules.forEach((module) => { + const { top, left } = getPosotion(module, direction, Number(length) + Number(moduleLength), false) + module.clone((obj) => { + obj.set({ + parentId: module.parentId, + initOptions: module.initOptions, + direction: module.direction, + arrow: module.arrow, + name: module.name, + type: module.type, + length: module.length, + points: module.points, + surfaceId: module.surfaceId, + left, + top, + id: uuidv4(), + }) + copyModule = obj + canvas.add(obj) + copyModules.push(obj) + obj.setCoords() + }) + if (isOverlapObjects(copyModule, objects) || isOutsideSurface(copyModule, moduleSetupSurface)) { + isWarning = true + copyModule.set({ fill: 'red' }) + } + canvas.renderAll() + }) + + if (isWarning) { + swalFire({ + title: getMessage('can.not.copy.module'), + icon: 'error', + type: 'alert', + confirmFn: () => { + canvas.remove(...copyModules) + canvas.renderAll() + }, + }) + } + } + + const moduleCopy = (length, direction) => { + if (canvas.getActiveObjects().length === 0) return + const activeModuleIds = canvas.getActiveObjects().map((obj) => obj.id) + const modules = canvas.getObjects().filter((obj) => activeModuleIds.includes(obj.id)) + const objects = getObjects() + const otherModules = canvas.getObjects().filter((obj) => obj.surfaceId === modules[0].surfaceId && obj.name === POLYGON_TYPE.MODULE) + const moduleSetupSurface = canvas + .getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && obj.id === modules[0].surfaceId)[0] + let isWarning = false + let copyModules = [] + let copyModule = null + canvas.discardActiveObject() //선택해제 + modules.forEach((module) => { + const { top, left } = getPosotion(module, direction, length, true) + module.clone((obj) => { + obj.set({ + parentId: module.parentId, + initOptions: module.initOptions, + direction: module.direction, + arrow: module.arrow, + name: module.name, + type: module.type, + length: module.length, + points: module.points, + surfaceId: module.surfaceId, + left, + top, + id: uuidv4(), + }) + copyModules.push(obj) + copyModule = obj + canvas.add(obj) + canvas.renderAll() + }) + + if ( + isOverlapOtherModules(copyModule, otherModules) || + isOverlapObjects(copyModule, objects) || + isOutsideSurface(copyModule, moduleSetupSurface) + ) { + isWarning = true + copyModule.set({ fill: 'red' }) + canvas.renderAll() + } + }) + + if (isWarning) { + swalFire({ + title: getMessage('can.not.copy.module'), + icon: 'error', + type: 'alert', + confirmFn: () => { + canvas.remove(...copyModules) + canvas.renderAll() + }, + }) + } + } + + const moduleMultiCopy = (type, length, direction) => { + if (canvas.getActiveObjects().length === 0) return + if (canvas.getActiveObjects().length > 1) { + swalFire({ + title: '여러 개의 모듈을 선택할 수 없습니다.', + icon: 'error', + type: 'alert', + }) + canvas.discardActiveObject() + return + } + const activeModule = canvas.getObjects().filter((obj) => canvas.getActiveObjects()[0].id === obj.id)[0] + const modules = type === 'row' ? getRowModules(activeModule) : getColumnModules(activeModule) + const otherModules = canvas.getObjects().filter((obj) => obj.surfaceId === modules[0].surfaceId && obj.name === POLYGON_TYPE.MODULE) + const objects = getObjects() + const copyModules = [] + let copyModule = null + const moduleSetupSurface = canvas + .getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && obj.id === modules[0].surfaceId)[0] + let isWarning = false + let moduleLength = 0 + if (['up', 'down'].includes(direction)) { + moduleLength = Number(modules[modules.length - 1].top) + Number(modules[modules.length - 1].height) - Number(modules[0].top) + } else if (['left', 'right'].includes(direction)) { + moduleLength = Number(modules[modules.length - 1].left) + Number(modules[modules.length - 1].width) - Number(modules[0].left) + } + + modules.forEach((module) => { + const { top, left } = getPosotion(module, direction, Number(length) + Number(moduleLength), false) + module.clone((obj) => { + obj.set({ + parentId: module.parentId, + initOptions: module.initOptions, + direction: module.direction, + arrow: module.arrow, + name: module.name, + type: module.type, + length: module.length, + points: module.points, + surfaceId: module.surfaceId, + left, + top, + id: uuidv4(), + }) + copyModule = obj + canvas.add(obj) + copyModules.push(obj) + obj.setCoords() + }) + if ( + isOverlapOtherModules(copyModule, otherModules) || + isOverlapObjects(copyModule, objects) || + isOutsideSurface(copyModule, moduleSetupSurface) + ) { + isWarning = true + copyModule.set({ fill: 'red' }) + } + canvas.renderAll() + }) + + if (isWarning) { + swalFire({ + title: getMessage('can.not.copy.module'), + icon: 'error', + type: 'alert', + confirmFn: () => { + canvas.remove(...copyModules) + canvas.renderAll() + }, + }) + } + } + + const moduleColumnRemove = (type) => { + const activeModule = canvas.getObjects().filter((obj) => canvas.getActiveObjects()[0].id === obj.id)[0] + const columnModules = getColumnModules(activeModule) + const otherModules = getOtherModules(columnModules) + const objects = getObjects() + let targetModules = [] + const rightModules = otherModules.filter((module) => activeModule.left < module.left).sort((a, b) => a.left - b.left) + const leftModules = otherModules.filter((module) => activeModule.left > module.left).sort((a, b) => b.left - a.left) + let width = -1 + const moduleSetupSurface = canvas + .getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && obj.id === activeModule.surfaceId)[0] + let isWarning = false + canvas.discardActiveObject() + canvas.remove(...columnModules) + canvas.renderAll() + + if (type === MODULE_REMOVE_TYPE.LEFT) { + rightModules.forEach((module) => { + module.originPos = { + left: module.left, + top: module.top, + fill: module.fill, + } + if (width === -1) width = module.left - activeModule.left + module.set({ left: module.left - width }) + module.setCoords() + canvas.renderAll() + if (isOverlapOtherModules(module, leftModules) || isOverlapObjects(module, objects) || isOutsideSurface(module, moduleSetupSurface)) { + module.set({ fill: 'red' }) + isWarning = true + } + }) + canvas.renderAll() + targetModules = rightModules + } else if (type === MODULE_REMOVE_TYPE.RIGHT) { + leftModules.forEach((module) => { + module.originPos = { + left: module.left, + top: module.top, + fill: module.fill, + } + if (width === -1) width = activeModule.left - module.left + module.set({ left: module.left + width }) + module.setCoords() + canvas.renderAll() + if (isOverlapOtherModules(module, rightModules) || isOverlapObjects(module, objects) || isOutsideSurface(module, moduleSetupSurface)) { + module.set({ fill: 'red' }) + isWarning = true + } + }) + canvas.renderAll() + targetModules = leftModules + } else if (type === MODULE_REMOVE_TYPE.HORIZONTAL_SIDE) { + const sideModules = [...leftModules, ...rightModules] + leftModules.forEach((module) => { + module.originPos = { + left: module.left, + top: module.top, + fill: module.fill, + } + if (width === -1) width = activeModule.left - module.left + module.set({ left: module.left + width / 2 }) + module.setCoords() + canvas.renderAll() + }) + + rightModules.forEach((module) => { + module.originPos = { + left: module.left, + top: module.top, + fill: module.fill, + } + if (width === -1) width = module.left - activeModule.left + module.set({ left: module.left - width / 2 }) + module.setCoords() + canvas.renderAll() + }) + + sideModules.forEach((module) => { + if ( + isOverlapOtherModules( + module, + sideModules.filter((m) => m.id !== module.id), + ) || + isOverlapObjects(module, objects) || + isOutsideSurface(module, moduleSetupSurface) + ) { + isWarning = true + module.set({ fill: 'red' }) + } + }) + + targetModules = sideModules + } + canvas.renderAll() + if (isWarning) { + swalFire({ + title: getMessage('can.not.remove.module'), + icon: 'error', + type: 'alert', + confirmFn: () => { + canvas.add(...columnModules) + targetModules.forEach((module) => { + module.set({ top: module.originPos.top, left: module.originPos.left, fill: module.originPos.fill }) + module.setCoords() + }) + canvas.renderAll() + }, + }) + } + } + + const moduleRowRemove = (type) => { + const activeModule = canvas.getObjects().filter((obj) => canvas.getActiveObjects()[0].id === obj.id)[0] + const rowModules = getRowModules(activeModule) + const otherModules = getOtherModules(rowModules) + const objects = getObjects() + let targetModules = [] + const topModules = otherModules.filter((module) => activeModule.top > module.top).sort((a, b) => b.top - a.top) + const bottomModules = otherModules.filter((module) => activeModule.top < module.top).sort((a, b) => a.top - b.top) + let height = -1 + const moduleSetupSurface = canvas + .getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && obj.id === activeModule.surfaceId)[0] + let isWarning = false + + canvas.discardActiveObject() + canvas.remove(...rowModules) + canvas.renderAll() + + if (type === MODULE_REMOVE_TYPE.TOP) { + bottomModules.forEach((module) => { + module.originPos = { + left: module.left, + top: module.top, + fill: module.fill, + } + if (height === -1) height = module.top - activeModule.top + module.set({ top: module.top - height }) + module.setCoords() + canvas.renderAll() + if (isOverlapOtherModules(module, topModules) || isOverlapObjects(module, objects) || isOutsideSurface(module, moduleSetupSurface)) { + isWarning = true + module.set({ fill: 'red' }) + } + }) + canvas.renderAll() + targetModules = bottomModules + } else if (type === MODULE_REMOVE_TYPE.BOTTOM) { + topModules.forEach((module) => { + module.originPos = { + left: module.left, + top: module.top, + fill: module.fill, + } + if (height === -1) height = activeModule.top - module.top + module.set({ top: module.top + activeModule.height }) + module.setCoords() + canvas.renderAll() + if (isOverlapOtherModules(module, bottomModules) || isOverlapObjects(module, objects) || isOutsideSurface(module, moduleSetupSurface)) { + isWarning = true + module.set({ fill: 'red' }) + } + }) + targetModules = topModules + } else if (type === MODULE_REMOVE_TYPE.VERTICAL_SIDE) { + topModules.forEach((module) => { + module.originPos = { + left: module.left, + top: module.top, + fill: module.fill, + } + // if (height === -1) height = activeModule.top - module.top + if (height === -1) height = activeModule.height + module.set({ top: module.top + height / 2 }) + module.setCoords() + }) + + bottomModules.forEach((module) => { + module.originPos = { + left: module.left, + top: module.top, + fill: module.fill, + } + // if (height === -1) height = module.top - activeModule.top + if (height === -1) height = activeModule.height + module.set({ top: module.top - height / 2 }) + module.setCoords() + }) + + canvas.renderAll() + const sideModules = [...topModules, ...bottomModules] + sideModules.forEach((module) => { + if ( + isOverlapOtherModules( + module, + sideModules.filter((m) => m.id !== module.id), + ) || + isOverlapObjects(module, objects) || + isOutsideSurface(module, moduleSetupSurface) + ) { + isWarning = true + module.set({ fill: 'red' }) + } + }) + targetModules = sideModules + } + canvas.renderAll() + if (isWarning && type !== MODULE_REMOVE_TYPE.NONE) { + targetModules.forEach((rect) => rect.set({ fill: 'red' })) + swalFire({ + title: getMessage('can.not.remove.module'), + icon: 'error', + type: 'alert', + confirmFn: () => { + canvas.add(...rowModules) + targetModules.forEach((module) => { + module.set({ top: module.originPos.top, left: module.originPos.left, fill: module.originPos.fill }) + module.setCoords() + }) + canvas.renderAll() + }, + }) + } + } + + const moduleColumnInsert = (type) => { + const activeModule = canvas.getObjects().filter((obj) => canvas.getActiveObjects()[0].id === obj.id)[0] + const columnModules = getColumnModules(activeModule) + let otherModules = getOtherModules(columnModules) + const targetModules = + type === MODULE_INSERT_TYPE.LEFT + ? otherModules.filter((module) => module.left < activeModule.left).sort((a, b) => a.left - b.left) + : otherModules.filter((module) => module.left > activeModule.left).sort((a, b) => a.left - b.left) + const objects = getObjects() + const copyModules = [] + const moduleSetupSurface = canvas + .getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && obj.id === activeModule.surfaceId)[0] + let width = -1 + let isWarning = false + if (targetModules.length === 0) { + swalFire({ + title: '마지막 모듈입니다.', + icon: 'error', + type: 'alert', + }) + return + } + canvas.discardActiveObject() + targetModules.forEach((module) => { + if (width === -1) + width = type === MODULE_INSERT_TYPE.LEFT ? Number(activeModule.left) - Number(module.left) : Number(module.left) - Number(activeModule.left) + const { top, left } = getPosotion(module, type, module.width, false) + module.originPos = { + left: module.left, + top: module.top, + fill: module.fill, + } + module.set({ left, top }) + canvas.renderAll() + if (isOverlapObjects(module, objects) || isOutsideSurface(module, moduleSetupSurface)) { + isWarning = true + module.set({ fill: 'red' }) + } + module.setCoords() + }) + canvas.renderAll() + otherModules = getOtherModules(columnModules) + columnModules.forEach((module) => { + const { top, left } = getPosotion(module, type, module.width, false) + let copyModule = null + module.clone((obj) => { + obj.set({ + parentId: module.parentId, + initOptions: module.initOptions, + direction: module.direction, + arrow: module.arrow, + name: module.name, + type: module.type, + length: module.length, + points: module.points, + surfaceId: module.surfaceId, + left, + top, + id: uuidv4(), + }) + copyModule = obj + canvas.add(obj) + copyModules.push(obj) + obj.setCoords() + }) + canvas.renderAll() + + if ( + isOverlapOtherModules(copyModule, otherModules) || + isOverlapObjects(copyModule, objects) || + isOutsideSurface(copyModule, moduleSetupSurface) + ) { + isWarning = true + } + module.setCoords() + }) + canvas.renderAll() + if (isWarning) { + swalFire({ + title: getMessage('can.not.insert.module'), + icon: 'error', + type: 'alert', + confirmFn: () => { + targetModules.forEach((module) => { + module.set({ top: module.originPos.top, left: module.originPos.left, fill: module.originPos.fill }) + module.setCoords() + }) + canvas.remove(...copyModules) + canvas.renderAll() + }, + }) + } + } + + const muduleRowInsert = (type) => { + const activeModule = canvas.getObjects().filter((obj) => canvas.getActiveObjects()[0].id === obj.id)[0] + const rowModules = getRowModules(activeModule) + let otherModules = getOtherModules(rowModules) + const targetModules = + type === MODULE_INSERT_TYPE.TOP + ? otherModules.filter((module) => module.top < activeModule.top).sort((a, b) => a.top - b.top) + : otherModules.filter((module) => module.top > activeModule.top).sort((a, b) => a.top - b.top) + if (targetModules.length === 0) { + swalFire({ + title: '마지막 모듈입니다.', + icon: 'error', + type: 'alert', + }) + return + } + const objects = getObjects() + const copyModules = [] + const moduleSetupSurface = canvas + .getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && obj.id === activeModule.surfaceId)[0] + let height = -1 + let isWarning = false + canvas.discardActiveObject() + targetModules.forEach((module) => { + if (height === -1) + height = type === MODULE_INSERT_TYPE.TOP ? Number(activeModule.top) - Number(module.top) : Number(module.top) - Number(activeModule.top) + const { top, left } = getPosotion(module, type, activeModule.height, false) + module.originPos = { + left: module.left, + top: module.top, + fill: module.fill, + } + module.set({ left, top }) + if (isOverlapObjects(module, objects) || isOutsideSurface(module, moduleSetupSurface)) { + isWarning = true + module.set({ fill: 'red' }) + } + module.setCoords() + }) + canvas.renderAll() + otherModules = getOtherModules(rowModules) + rowModules.forEach((module) => { + const { top, left } = getPosotion(module, type, activeModule.height, false) + let copyModule = null + module.clone((obj) => { + obj.set({ + parentId: module.parentId, + initOptions: module.initOptions, + direction: module.direction, + arrow: module.arrow, + name: module.name, + type: module.type, + length: module.length, + points: module.points, + surfaceId: module.surfaceId, + fill: module.fill, + left, + top, + id: uuidv4(), + }) + copyModule = obj + canvas.add(obj) + copyModules.push(obj) + obj.setCoords() + }) + canvas.renderAll() + + if ( + isOverlapOtherModules(copyModule, otherModules) || + isOverlapObjects(copyModule, objects) || + isOutsideSurface(copyModule, moduleSetupSurface) + ) { + isWarning = true + copyModule.set({ fill: 'red' }) + } + module.setCoords() + }) + canvas.renderAll() + + if (isWarning) { + swalFire({ + title: getMessage('can.not.insert.module'), + icon: 'error', + type: 'alert', + confirmFn: () => { + targetModules.forEach((module) => { + module.set({ top: module.originPos.top, left: module.originPos.left, fill: module.originPos.fill }) + module.setCoords() + }) + canvas.remove(...copyModules) + canvas.renderAll() + }, + }) + } + } + + const alignModule = (type) => { + const moduleSetupSurface = canvas.getObjects().filter((obj) => canvas.getActiveObjects()[0].id === obj.id)[0] + const modules = canvas.getObjects().filter((obj) => obj.surfaceId === moduleSetupSurface.id && obj.name === POLYGON_TYPE.MODULE) + const objects = getObjects() + let [top, bottom, left, right] = [0, 0, 0, 0] + + top = Math.min(...modules.map((module) => module.top)) + bottom = Math.max(...modules.map((module) => module.top + module.height)) + left = Math.min(...modules.map((module) => module.left)) + right = Math.max(...modules.map((module) => module.left + module.width)) + const moduleSurfacePos = { + top: Math.min(...moduleSetupSurface.points.map((point) => point.y)), + left: Math.min(...moduleSetupSurface.points.map((point) => point.x)), + } + const [height, width] = [bottom - top, right - left] + const verticalCenterLength = moduleSurfacePos.top + moduleSetupSurface.height / 2 - (top + height / 2) + const horizontalCenterLength = moduleSurfacePos.left + moduleSetupSurface.width / 2 - (left + width / 2) + let isWarning = false + + canvas.discardActiveObject() + modules.forEach((module) => { + module.originPos = { + left: module.left, + top: module.top, + fill: module.fill, + } + if (type === MODULE_ALIGN_TYPE.VERTICAL) { + module.set({ top: module.top + verticalCenterLength }) + } else if (type === MODULE_ALIGN_TYPE.HORIZONTAL) { + module.set({ left: module.left + horizontalCenterLength }) + } + + canvas.renderAll() + module.setCoords() + if (isOverlapObjects(module, objects) || isOutsideSurface(module, moduleSetupSurface)) { + isWarning = true + module.set({ fill: 'red' }) + } + }) + canvas.renderAll() + if (isWarning) { + swalFire({ + title: getMessage('can.not.align.module'), + icon: 'error', + type: 'alert', + confirmFn: () => { + modules.forEach((module) => { + module.set({ top: module.originPos.top, left: module.originPos.left, fill: module.originPos.fill }) + module.setCoords() + }) + canvas.renderAll() + }, + }) + } + } + + const modulesRemove = () => { + const activeModule = canvas.getObjects().filter((obj) => canvas.getActiveObjects()[0].id === obj.id)[0] + const modules = canvas.getObjects().filter((obj) => obj.surfaceId === activeModule.surfaceId && obj.name === POLYGON_TYPE.MODULE) + canvas.remove(...modules) + canvas.renderAll() + } + + const isOverlapOtherModules = (module, otherModules) => { + return otherModules.some( + (otherModule) => + turf.booleanOverlap(polygonToTurfPolygon(module, true), polygonToTurfPolygon(otherModule, true)) || + turf.booleanWithin(polygonToTurfPolygon(module, true), polygonToTurfPolygon(otherModule, true)), + ) + } + + const isOverlapObjects = (module, objects) => { + return !checkModuleDisjointObjects(polygonToTurfPolygon(module, true), objects) + } + + const isOutsideSurface = (module, moduleSetupSurface) => { + return ( + !turf.booleanContains(polygonToTurfPolygon(moduleSetupSurface, true), polygonToTurfPolygon(module, true)) || + !turf.booleanWithin(polygonToTurfPolygon(module, true), polygonToTurfPolygon(moduleSetupSurface, true)) + ) + } + + const getRowModules = (target) => { + return canvas + .getObjects() + .filter((obj) => target.surfaceId === obj.surfaceId && obj.name === POLYGON_TYPE.MODULE && obj.top === target.top) + .sort((a, b) => a.left - b.left) + } + + const getColumnModules = (target) => { + return canvas + .getObjects() + .filter((obj) => target.surfaceId === obj.surfaceId && obj.name === POLYGON_TYPE.MODULE && obj.left === target.left) + .sort((a, b) => a.top - b.top) + } + + const getPosotion = (target, direction, length, hasMargin = false) => { + let top = target.top + let left = target.left + + if (direction === 'up') { + top = Number(target.top) - Number(length) + top = hasMargin ? top - Number(target.height) : top + } else if (direction === 'down') { + top = Number(target.top) + Number(length) + top = hasMargin ? top + Number(target.height) : top + } else if (direction === 'left') { + left = Number(target.left) - Number(length) + left = hasMargin ? left - Number(target.width) : left + } else if (direction === 'right') { + left = Number(target.left) + Number(length) + left = hasMargin ? left + Number(target.width) : left + } + return { top, left } + } + + const getOtherModules = (modules) => { + const moduleIds = modules.map((module) => module.id) + return canvas + .getObjects() + .filter((obj) => obj.surfaceId === modules[0].surfaceId && obj.name === POLYGON_TYPE.MODULE && !moduleIds.includes(obj.id)) + } + + const getObjects = () => { + return canvas + ?.getObjects() + .filter((obj) => [BATCH_TYPE.OPENING, BATCH_TYPE.TRIANGLE_DORMER, BATCH_TYPE.PENTAGON_DORMER, BATCH_TYPE.SHADOW].includes(obj.name)) + } + + return { + moduleMove, + moduleMultiMove, + moduleMoveAll, + moduleCopy, + moduleMultiCopy, + moduleCopyAll, + moduleColumnRemove, + moduleRowRemove, + moduleColumnInsert, + muduleRowInsert, + modulesRemove, + alignModule, + } +} diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index e54b227f..cbd740bf 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -1,7 +1,7 @@ import { useRecoilState, useRecoilValue } from 'recoil' import { canvasState } from '@/store/canvasAtom' -import { rectToPolygon, setSurfaceShapePattern } from '@/util/canvas-util' -import { roofDisplaySelector } from '@/store/settingAtom' +import { rectToPolygon, setSurfaceShapePattern, polygonToTurfPolygon } from '@/util/canvas-util' +import { basicSettingState, roofDisplaySelector } from '@/store/settingAtom' import offsetPolygon, { calculateAngle } from '@/util/qpolygon-utils' import { QPolygon } from '@/components/fabric/QPolygon' import { moduleSetupSurfaceState, moduleIsSetupState } from '@/store/canvasAtom' @@ -13,6 +13,8 @@ import { useSwal } from '@/hooks/useSwal' import { canvasSettingState } from '@/store/canvasAtom' import { compasDegAtom } from '@/store/orientationAtom' import { QLine } from '@/components/fabric/QLine' +import { useRoofFn } from '@/hooks/common/useRoofFn' +import { useEffect } from 'react' export function useModuleBasicSetting() { const canvas = useRecoilValue(canvasState) @@ -23,10 +25,38 @@ export function useModuleBasicSetting() { const { swalFire } = useSwal() const canvasSetting = useRecoilValue(canvasSettingState) const compasDeg = useRecoilValue(compasDegAtom) + const { setSurfaceShapePattern } = useRoofFn() + const [basicSetting, setBasicSettings] = useRecoilState(basicSettingState) + + useEffect(() => { + // console.log('basicSetting', basicSetting) + if (canvas) { + canvas.selection = true + canvas.selectionFullyContained = true + // canvas.on('selection:created', (e) => { + // console.log('selection:created', e.selected) + // }) + } + }, []) // const { addTargetMouseEventListener, addCanvasMouseEventListener, initEvent } = useContext(EventContext) let selectedModuleInstSurfaceArray = [] + const moduleOptions = { + fill: '#BFFD9F', + stroke: 'black', + strokeWidth: 0.1, + selectable: true, // 선택 가능하게 설정 + lockMovementX: true, // X 축 이동 잠금 + lockMovementY: true, // Y 축 이동 잠금 + lockRotation: true, // 회전 잠금 + lockScalingX: true, // X 축 크기 조정 잠금 + lockScalingY: true, // Y 축 크기 조정 잠금 + parentId: moduleSetupSurface.parentId, + surfaceId: moduleSetupSurface.id, + name: 'module', + } + //모듈,회로에서 다른메뉴 -> 배치면으로 갈 경수 초기화 const restoreModuleInstArea = () => { //설치면 삭제 @@ -97,15 +127,17 @@ export function useModuleBasicSetting() { setupSurface.setViewLengthText(false) canvas.add(setupSurface) //모듈설치면 만들기 - const flowLines = { - bottom: bottomTopFlowLine(setupSurface).find((obj) => obj.target === 'bottom'), - top: bottomTopFlowLine(setupSurface).find((obj) => obj.target === 'top'), - left: leftRightFlowLine(setupSurface).find((obj) => obj.target === 'left'), - right: leftRightFlowLine(setupSurface).find((obj) => obj.target === 'right'), - } + //육지붕이 아닐때만 넣는다 육지붕일땐 클릭 이벤트에 별도로 넣어놓음 + if (canvasSetting.roofSizeSet !== 3) { + const flowLines = { + bottom: bottomTopFlowLine(setupSurface).find((obj) => obj.target === 'bottom'), + top: bottomTopFlowLine(setupSurface).find((obj) => obj.target === 'top'), + left: leftRightFlowLine(setupSurface).find((obj) => obj.target === 'left'), + right: leftRightFlowLine(setupSurface).find((obj) => obj.target === 'right'), + } - setupSurface.set({ flowLines: flowLines }) - flatRoofMakeSurface(roof, setupSurface) + setupSurface.set({ flowLines: flowLines }) + } //지붕면 선택 금지 roof.set({ @@ -172,21 +204,6 @@ export function useModuleBasicSetting() { obj.name === BATCH_TYPE.SHADOW, ) //도머s 객체 - const moduleOptions = { - fill: '#BFFD9F', - stroke: 'black', - strokeWidth: 0.1, - selectable: false, // 선택 가능하게 설정 - lockMovementX: true, // X 축 이동 잠금 - lockMovementY: true, // Y 축 이동 잠금 - lockRotation: true, // 회전 잠금 - lockScalingX: true, // X 축 크기 조정 잠금 - lockScalingY: true, // Y 축 크기 조정 잠금 - opacity: 0.8, - parentId: moduleSetupSurface.parentId, - name: 'module', - } - if (moduleSetupSurfaces.length !== 0) { let tempModule let manualDrawModules = [] @@ -225,7 +242,7 @@ export function useModuleBasicSetting() { tempModule = new fabric.Rect({ fill: 'white', stroke: 'black', - strokeWidth: 1, + strokeWidth: 0.3, width: width, height: height, left: mousePoint.x - width / 2, @@ -236,7 +253,6 @@ export function useModuleBasicSetting() { lockRotation: true, lockScalingX: true, lockScalingY: true, - opacity: 0.8, name: 'tempModule', parentId: moduleSetupSurfaces[i].parentId, }) @@ -277,22 +293,22 @@ export function useModuleBasicSetting() { //설치된 셀에 좌측에 스냅 if (Math.abs(smallRight - holdCellLeft) < snapDistance) { - tempModule.left = holdCellLeft - width - 0.5 + tempModule.left = holdCellLeft - width - 1 } //설치된 셀에 우측에 스냅 if (Math.abs(smallLeft - holdCellRight) < snapDistance) { - tempModule.left = holdCellRight + 0.5 + tempModule.left = holdCellRight + 1 } //설치된 셀에 위쪽에 스냅 if (Math.abs(smallBottom - holdCellTop) < snapDistance) { - tempModule.top = holdCellTop - height - 0.5 + tempModule.top = holdCellTop - height - 1 } //설치된 셀에 밑쪽에 스냅 if (Math.abs(smallTop - holdCellBottom) < snapDistance) { - tempModule.top = holdCellBottom + 0.5 + tempModule.top = holdCellBottom + 1 } //가운데 -> 가운데 if (Math.abs(smallCenterX - holdCellCenterX) < cellSnapDistance) { @@ -310,14 +326,14 @@ export function useModuleBasicSetting() { 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 - holdCellCenterY) < cellSnapDistance) { + // tempModule.top = holdCellCenterY + // } + // //아랫쪽 -> 가운데 + // if (Math.abs(smallBottom - holdCellCenterY) < cellSnapDistance) { + // tempModule.top = holdCellCenterY - height + // } }) } @@ -391,13 +407,13 @@ export function useModuleBasicSetting() { 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, y: tempModule.top }, + { x: tempModule.left + tempModule.width * tempModule.scaleX, y: tempModule.top }, { - x: tempModule.left + tempModule.width * tempModule.scaleX + 0.5, - y: tempModule.top + tempModule.height * tempModule.scaleY + 0.5, + x: tempModule.left + tempModule.width * tempModule.scaleX, + y: tempModule.top + tempModule.height * tempModule.scaleY, }, - { x: tempModule.left + 0.5, y: tempModule.top + tempModule.height * tempModule.scaleY + 0.5 }, + { x: tempModule.left, y: tempModule.top + tempModule.height * tempModule.scaleY }, ] tempModule.set({ points: rectPoints }) @@ -413,7 +429,8 @@ export function useModuleBasicSetting() { dormerTurfPolygon = batchObjectGroupToTurfPolygon(object) } else { //개구, 그림자 - dormerTurfPolygon = polygonToTurfPolygon(rectToPolygon(object)) + object.set({ points: rectToPolygon(object) }) + dormerTurfPolygon = polygonToTurfPolygon(object) } const intersection = turf.intersect(turf.featureCollection([dormerTurfPolygon, tempTurfModule])) //겹치는지 확인 @@ -436,6 +453,7 @@ export function useModuleBasicSetting() { canvas?.remove(tempModule) //안겹치면 넣는다 // tempModule.setCoords() + moduleOptions.surfaceId = trestlePolygon.id let manualModule = new QPolygon(tempModule.points, { ...moduleOptions }) canvas?.add(manualModule) manualDrawModules.push(manualModule) @@ -512,15 +530,15 @@ export function useModuleBasicSetting() { const moduleOptions = { fill: '#BFFD9F', stroke: 'black', - strokeWidth: 0.1, - selectable: false, // 선택 가능하게 설정 + strokeWidth: 0.3, + selectable: true, // 선택 가능하게 설정 lockMovementX: true, // X 축 이동 잠금 lockMovementY: true, // Y 축 이동 잠금 lockRotation: true, // 회전 잠금 lockScalingX: true, // X 축 크기 조정 잠금 lockScalingY: true, // Y 축 크기 조정 잠금 - opacity: 0.8, parentId: moduleSetupSurface.parentId, + surfaceId: moduleSetupSurface.id, name: 'module', } @@ -549,35 +567,35 @@ export function useModuleBasicSetting() { return containsBatchObjects } - /** - * 도머나 개구가 모듈에 걸치는지 확인하는 로직 - * @param {*} squarePolygon - * @param {*} containsBatchObjects - * @returns - */ - const checkModuleDisjointObjects = (squarePolygon, containsBatchObjects) => { - let isDisjoint = false - - if (containsBatchObjects.length > 0) { - let convertBatchObject - //도머가 있으면 적용되는 로직 - isDisjoint = containsBatchObjects.every((batchObject) => { - if (batchObject.type === 'group') { - convertBatchObject = batchObjectGroupToTurfPolygon(batchObject) - } else { - convertBatchObject = polygonToTurfPolygon(batchObject) - } - /** - * 도머가 여러개일수있으므로 겹치는게 있다면... - * 안겹치는지 확인하는 로직이라 안겹치면 true를 반환 - */ - return turf.booleanDisjoint(squarePolygon, convertBatchObject) - }) - } else { - isDisjoint = true - } - return isDisjoint - } + // /** + // * 도머나 개구가 모듈에 걸치는지 확인하는 로직 + // * @param {*} squarePolygon + // * @param {*} containsBatchObjects + // * @returns + // */ + // const checkModuleDisjointObjects = (squarePolygon, containsBatchObjects) => { + // let isDisjoint = false + // + // if (containsBatchObjects.length > 0) { + // let convertBatchObject + // //도머가 있으면 적용되는 로직 + // isDisjoint = containsBatchObjects.every((batchObject) => { + // if (batchObject.type === 'group') { + // convertBatchObject = batchObjectGroupToTurfPolygon(batchObject) + // } else { + // convertBatchObject = polygonToTurfPolygon(batchObject) + // } + // /** + // * 도머가 여러개일수있으므로 겹치는게 있다면... + // * 안겹치는지 확인하는 로직이라 안겹치면 true를 반환 + // */ + // return turf.booleanDisjoint(squarePolygon, convertBatchObject) + // }) + // } else { + // isDisjoint = true + // } + // return isDisjoint + // } /** * 배치면 안에 있는지 확인 @@ -589,7 +607,8 @@ export function useModuleBasicSetting() { return turf.booleanContains(turfModuleSetupSurface, squarePolygon) || turf.booleanWithin(squarePolygon, turfModuleSetupSurface) } - const downFlowSetupModule = (surfaceMaxLines, width, height, moduleSetupArray, flowModuleLine, isCenter = false) => { + const downFlowSetupModule = (surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface, isCenter = false) => { + const flowModuleLine = moduleSetupSurface.flowLines let startPoint = flowModuleLine.bottom if (isCenter) { @@ -633,9 +652,9 @@ export function useModuleBasicSetting() { if (isMaxSetup) totalWidth = totalWidth * 2 //최대배치시 2배로 늘려서 반씩 검사하기위함 for (let j = 0; j < diffTopEndPoint; j++) { - bottomMargin = j === 0 ? 1 : 2 + bottomMargin = 1 * j for (let i = 0; i <= totalWidth; i++) { - leftMargin = i === 0 ? 1 : 2 + leftMargin = 1 * i chidoriLength = 0 if (isChidori) { chidoriLength = j % 2 === 0 ? 0 : width / 2 @@ -653,6 +672,7 @@ export function useModuleBasicSetting() { let turfCoordnates = squarePolygon.geometry.coordinates[0].slice(0, -1) let points = turfCoordnates.map((coord) => ({ x: coord[0], y: coord[1] })) + moduleOptions.surfaceId = moduleSetupSurface.id let tempModule = new QPolygon(points, { ...moduleOptions, turfPoints: squarePolygon }) canvas?.add(tempModule) moduleSetupArray.push(tempModule) @@ -660,7 +680,8 @@ export function useModuleBasicSetting() { } } - const leftFlowSetupModule = (surfaceMaxLines, width, height, moduleSetupArray, aaa, isCenter = false) => { + const leftFlowSetupModule = (surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface, isCenter = false) => { + const flowModuleLine = moduleSetupSurface.flowLines let startPoint = flowModuleLine.left //중앙배치일 경우에는 계산한다 @@ -714,6 +735,7 @@ export function useModuleBasicSetting() { let points = turfCoordnates.map((coord) => ({ x: coord[0], y: coord[1] })) // if (disjointFromTrestle && isDisjoint) { + moduleOptions.surfaceId = moduleSetupSurface.id let tempModule = new QPolygon(points, { ...moduleOptions, turfPoints: squarePolygon }) canvas?.add(tempModule) moduleSetupArray.push(tempModule) @@ -721,7 +743,8 @@ export function useModuleBasicSetting() { } } - const topFlowSetupModule = (surfaceMaxLines, width, height, moduleSetupArray, flowModuleLine, isCenter = false) => { + const topFlowSetupModule = (surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface, isCenter = false) => { + const flowModuleLine = moduleSetupSurface.flowLines let startPoint = flowModuleLine.top if (isCenter) { @@ -785,6 +808,7 @@ export function useModuleBasicSetting() { let points = turfCoordnates.map((coord) => ({ x: coord[0], y: coord[1] })) // if (disjointFromTrestle && isDisjoint) { + moduleOptions.surfaceId = moduleSetupSurface.id let tempModule = new QPolygon(points, { ...moduleOptions, turfPoints: squarePolygon }) canvas?.add(tempModule) moduleSetupArray.push(tempModule) @@ -792,7 +816,8 @@ export function useModuleBasicSetting() { } } - const rightFlowSetupModule = (surfaceMaxLines, width, height, moduleSetupArray, flowModuleLine, isCenter = false) => { + const rightFlowSetupModule = (surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface, isCenter = false) => { + const flowModuleLine = moduleSetupSurface.flowLines let startPoint = flowModuleLine.right if (isCenter) { @@ -846,6 +871,7 @@ export function useModuleBasicSetting() { let points = turfCoordnates.map((coord) => ({ x: coord[0], y: coord[1] })) // if (disjointFromTrestle && isDisjoint) { + moduleOptions.surfaceId = moduleSetupSurface.id let tempModule = new QPolygon(points, { ...moduleOptions, turfPoints: squarePolygon }) canvas?.add(tempModule) moduleSetupArray.push(tempModule) @@ -879,44 +905,44 @@ export function useModuleBasicSetting() { if (setupLocation === 'eaves') { // 흐름방향이 남쪽일때 if (moduleSetupSurface.flowDirection === 'south') { - downFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface.flowLines) + downFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface) } if (moduleSetupSurface.flowDirection === 'west') { - leftFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface.flowLines) + leftFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface) } if (moduleSetupSurface.flowDirection === 'east') { - rightFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface.flowLines) + rightFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface) } if (moduleSetupSurface.flowDirection === 'north') { - topFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface.flowLines) + topFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface) } } else if (setupLocation === 'ridge') { //용마루 if (moduleSetupSurface.flowDirection === 'south') { - topFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface.flowLines) + topFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface) } if (moduleSetupSurface.flowDirection === 'west') { - rightFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface.flowLines) + rightFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface) } if (moduleSetupSurface.flowDirection === 'east') { - leftFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface.flowLines) + leftFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface) } if (moduleSetupSurface.flowDirection === 'north') { - downFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface.flowLines) + downFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface) } } else if (setupLocation === 'center') { //중가면 if (moduleSetupSurface.flowDirection === 'south') { - downFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface.flowLines, true) + downFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface, true) } if (moduleSetupSurface.flowDirection === 'west') { - leftFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface.flowLines, true) + leftFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface, true) } if (moduleSetupSurface.flowDirection === 'east') { - rightFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface.flowLines, true) + rightFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface, true) } if (moduleSetupSurface.flowDirection === 'north') { - topFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface.flowLines, true) + topFlowSetupModule(surfaceMaxLines, width, height, moduleSetupArray, moduleSetupSurface, true) } } @@ -1210,20 +1236,6 @@ export function useModuleBasicSetting() { return turf.polygon([coordinates]) } - const polygonToTurfPolygon = (object, current = false) => { - let coordinates - coordinates = object.points.map((point) => [point.x, point.y]) - if (current) coordinates = object.getCurrentPoints().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 = [] @@ -1306,11 +1318,11 @@ export function useModuleBasicSetting() { 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, - // }) + const finalLine = new QLine([pointX1, pointY1, pointX2, pointY2], { + stroke: 'red', + strokeWidth: 1, + selectable: true, + }) // canvas?.add(finalLine) // canvas?.renderAll() @@ -1429,11 +1441,11 @@ export function useModuleBasicSetting() { 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, - // }) + const finalLine = new QLine([pointX1, pointY1, pointX2, pointY2], { + stroke: 'red', + strokeWidth: 1, + selectable: true, + }) // canvas?.add(finalLine) // canvas?.renderAll() @@ -1526,27 +1538,8 @@ export function useModuleBasicSetting() { } const manualFlatroofModuleSetup = (placementFlatRef) => { - const moduleSetupSurfaces = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) //모듈설치면를 가져옴 - let applyAngle + let moduleSetupSurfaces = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) //모듈설치면를 가져옴 let flatBatchType = placementFlatRef.setupLocation.current.value - let excretaLinesAngle = [] - - if (flatBatchType === 'excreta') { - const excretaLines = canvas.getObjects().filter((obj) => obj.name === 'flatExcretaLine') - excretaLines.forEach((obj) => { - if (obj.isSelected) { - const points1 = { x: obj.x1, y: obj.y1 } - const points2 = { x: obj.x2, y: obj.y2 } - excretaLinesAngle.push({ - surfaceId: obj.surfaceId, - angle: calculateAngle(points1, points2), - }) - } - canvas.remove(obj) - }) - } - - //calculateAngle const batchObjects = canvas ?.getObjects() @@ -1562,41 +1555,17 @@ export function useModuleBasicSetting() { fill: '#BFFD9F', stroke: 'black', strokeWidth: 0.1, - selectable: false, // 선택 가능하게 설정 + selectable: true, // 선택 가능하게 설정 lockMovementX: true, // X 축 이동 잠금 lockMovementY: true, // Y 축 이동 잠금 lockRotation: true, // 회전 잠금 lockScalingX: true, // X 축 크기 조정 잠금 lockScalingY: true, // Y 축 크기 조정 잠금 - opacity: 0.8, parentId: moduleSetupSurface.parentId, + surfaceId: moduleSetupSurface.id, name: 'module', } - function getRotatedCorners(rect) { - // 사각형의 중심점 - const center = rect.getCenterPoint() - - // 사각형의 원래 꼭짓점 좌표 (로컬 좌표 기준) - const halfWidth = (rect.width / 2) * rect.scaleX - const halfHeight = (rect.height / 2) * rect.scaleY - - const corners = [ - { x: -halfWidth, y: -halfHeight }, // 좌상단 - { x: halfWidth, y: -halfHeight }, // 우상단 - { x: halfWidth, y: halfHeight }, // 우하단 - { x: -halfWidth, y: halfHeight }, // 좌하단 - ] - - // 각 꼭짓점 좌표를 캔버스 좌표로 변환 - const transformedCorners = corners.map((corner) => { - const point = new fabric.Point(corner.x, corner.y) - return fabric.util.transformPoint(point, rect.calcTransformMatrix()) - }) - - return transformedCorners - } - if (moduleSetupSurfaces.length !== 0) { let tempModule let manualDrawModules = [] @@ -1605,6 +1574,37 @@ export function useModuleBasicSetting() { let flowDirection let trestlePolygon + //남쪽 선택 + if (flatBatchType === 'excreta') { + //변별로 선택 + const excretaLines = canvas.getObjects().filter((obj) => obj.name === 'flatExcretaLine') + excretaLines.forEach((obj) => { + if (obj.isSelected === true) { + const points1 = { x: obj.x1, y: obj.y1 } + const points2 = { x: obj.x2, y: obj.y2 } + const angle = calculateAngle(points1, points2) + + //변별로 선택으로 되어있을때 모듈면을 회전시키기 + const targetdSurface = moduleSetupSurfaces.filter((surface) => surface.surfaceId === obj.surfaceId)[0] + targetdSurface.angle = -angle + //변별로 선택되어있는 지붕도 회전시키기 + const targetRoof = canvas.getObjects().filter((roof) => roof.name === POLYGON_TYPE.ROOF && roof.id === targetdSurface.parentId)[0] + targetRoof.angle = -angle + + targetRoof.fire('modified') + targetdSurface.fire('modified') + } + canvas.remove(obj) + }) + } else { + moduleSetupSurfaces.forEach((surface) => { + const targetRoof = canvas.getObjects().filter((roof) => roof.name === POLYGON_TYPE.ROOF && roof.id === surface.parentId)[0] + if (targetRoof) targetRoof.angle = -compasDeg + surface.angle = -compasDeg + }) + } + canvas.renderAll() + addCanvasMouseEventListener('mouse:move', (e) => { //마우스 이벤트 삭제 후 재추가 const mousePoint = canvas.getPointer(e.e) @@ -1614,32 +1614,16 @@ export function useModuleBasicSetting() { trestlePolygon = moduleSetupSurfaces[i] manualDrawModules = moduleSetupSurfaces[i].modules // 앞에서 자동으로 했을때 추가됨 flowDirection = moduleSetupSurfaces[i].flowDirection //도형의 방향 - - if (flatBatchType === 'excreta') { - const tempLine = excretaLinesAngle.find((obj) => obj.surfaceId === trestlePolygon.surfaceId) - if (tempLine) { - applyAngle = tempLine.angle - } else { - //혹시나 두개의 지붕을 그리고 한쪽면만 선택했을때 한면은 그냥 기본 기울기를 준다 - applyAngle = compasDeg - } - } - let width = flowDirection === 'south' || flowDirection === 'north' ? 172 : 113 let height = flowDirection === 'south' || flowDirection === 'north' ? 113 : 172 - const angledModule = new fabric.Rect({ - width: width, - height: height, - left: mousePoint.x - width / 2, - top: mousePoint.y - height / 2, - }) + 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 center = angledModule.getCenterPoint() - angledModule.set('angle', applyAngle) - angledModule.setPositionByOrigin(center, 'center', 'center') - - const points = getRotatedCorners(angledModule) // const turfPoints = coordToTurfPolygon(points) if (turf.booleanWithin(turfPoints, turfPolygon)) { @@ -1648,15 +1632,25 @@ export function useModuleBasicSetting() { if (isDrawing) return canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tempModule')) //움직일때 일단 지워가면서 움직임 - tempModule = new QPolygon(points, { + tempModule = new fabric.Rect({ fill: 'white', stroke: 'black', strokeWidth: 0.3, + 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, name: 'tempModule', + parentId: moduleSetupSurfaces[i].parentId, }) canvas?.add(tempModule) //움직여가면서 추가됨 - canvas?.renderAll() /** * 스냅기능 @@ -1664,126 +1658,129 @@ export function useModuleBasicSetting() { let snapDistance = 10 let cellSnapDistance = 20 - // if (applyAngle === 90 || applyAngle === 180 || applyAngle === 270 || applyAngle === 0) { - // 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 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 + // 작은 폴리곤의 경계 좌표 계산 + 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 (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(smallRight - holdCellLeft) < snapDistance) { + tempModule.left = holdCellLeft - width - 1 + } - // //설치된 셀에 우측에 스냅 - // if (Math.abs(smallLeft - holdCellRight) < snapDistance) { - // tempModule.left = holdCellRight + 0.5 - // } + //설치된 셀에 우측에 스냅 + if (Math.abs(smallLeft - holdCellRight) < snapDistance) { + tempModule.left = holdCellRight + 1 + } - // //설치된 셀에 위쪽에 스냅 - // if (Math.abs(smallBottom - holdCellTop) < snapDistance) { - // tempModule.top = holdCellTop - height - 0.5 - // } + //설치된 셀에 위쪽에 스냅 + if (Math.abs(smallBottom - holdCellTop) < snapDistance) { + tempModule.top = holdCellTop - height - 1 + } - // //설치된 셀에 밑쪽에 스냅 - // 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 - holdCellBottom) < snapDistance) { + tempModule.top = holdCellBottom + 1 + } + //가운데 -> 가운데 + 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 - 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(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 (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 (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(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(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 - // } - // } - // } + 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 { @@ -1802,6 +1799,17 @@ export function useModuleBasicSetting() { let isIntersection = true if (!inside) return if (tempModule) { + const rectPoints = [ + { x: tempModule.left, y: tempModule.top }, + { x: tempModule.left + tempModule.width * tempModule.scaleX, y: tempModule.top }, + { + x: tempModule.left + tempModule.width * tempModule.scaleX, + y: tempModule.top + tempModule.height * tempModule.scaleY, + }, + { x: tempModule.left, y: tempModule.top + tempModule.height * tempModule.scaleY }, + ] + + tempModule.set({ points: rectPoints }) const tempTurfModule = polygonToTurfPolygon(tempModule) //도머 객체를 가져옴 @@ -1832,6 +1840,7 @@ export function useModuleBasicSetting() { //마우스 클릭시 set으로 해당 위치에 셀을 넣음 const isOverlap = manualDrawModules.some((module) => turf.booleanOverlap(tempTurfModule, polygonToTurfPolygon(module))) //겹치는지 확인 if (!isOverlap) { + moduleOptions.surfaceId = trestlePolygon.id let manualModule = new QPolygon(tempModule.points, { ...moduleOptions }) canvas?.add(manualModule) manualDrawModules.push(tempModule) @@ -1849,9 +1858,7 @@ export function useModuleBasicSetting() { const autoFlatroofModuleSetup = (placementFlatRef) => { initEvent() //마우스 이벤트 초기화 - let flatBatchType = placementFlatRef.setupLocation.current.value const moduleSetupSurfaces = moduleSetupSurface //선택 설치면 - const notSelectedTrestlePolygons = canvas ?.getObjects() .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && !moduleSetupSurfaces.includes(obj)) //설치면이 아닌것 @@ -1866,10 +1873,10 @@ export function useModuleBasicSetting() { obj.name === BATCH_TYPE.SHADOW, ) //도머s 객체 - if (moduleSetupSurfaces.length === 0) { - alert('선택된 모듈 설치면이 없습니다.') - return - } + // if (moduleSetupSurfaces.length === 0) { + // alert('선택된 모듈 설치면이 없습니다.') + // return + // } //어짜피 자동으로 누르면 선택안된데도 다 날아간다 canvas.getObjects().forEach((obj) => { @@ -1887,22 +1894,86 @@ export function useModuleBasicSetting() { } }) + const flatBatchType = placementFlatRef.setupLocation.current.value + + //남쪽 선택 + if (flatBatchType === 'excreta') { + //변별로 선택 + const excretaLines = canvas.getObjects().filter((obj) => obj.name === 'flatExcretaLine') + excretaLines.forEach((obj) => { + if (obj.isSelected === true) { + const points1 = { x: obj.x1, y: obj.y1 } + const points2 = { x: obj.x2, y: obj.y2 } + const angle = calculateAngle(points1, points2) + + // const targetdSurface = moduleSetupSurfaces.filter((surface) => surface.surfaceId === obj.surfaceId)[0] + const targetSurface = canvas + .getObjects() + .filter((surface) => surface.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && surface.surfaceId === obj.surfaceId)[0] + const targetRoof = canvas.getObjects().filter((roof) => roof.name === POLYGON_TYPE.ROOF && roof.id === targetSurface.parentId)[0] + + targetRoof.angle = -angle + targetSurface.angle = -angle + + targetRoof.fire('modified') + targetSurface.fire('modified') + moduleSetupSurfaces.push(targetSurface) + } + canvas.remove(obj) + }) + } else { + moduleSetupSurfaces.forEach((surface) => { + const targetRoof = canvas.getObjects().filter((roof) => roof.name === POLYGON_TYPE.ROOF && roof.id === surface.parentId)[0] + if (targetRoof) targetRoof.angle = -compasDeg + surface.angle = -compasDeg + }) + } + canvas.renderAll() + + moduleSetupSurfaces.forEach((surface) => { + let currentPoints = surface.getCurrentPoints() + let lines = [] + + for (let i = 0; i < currentPoints.length; i++) { + const start = currentPoints[i] + const end = currentPoints[(i + 1) % currentPoints.length] + const line = new QLine([start.x, start.y, end.x, end.y], {}) + lines.push(line) + } + + surface.lines.forEach((targetLine, index) => { + targetLine.x1 = lines[index].x1 + targetLine.y1 = lines[index].y1 + targetLine.x2 = lines[index].x2 + targetLine.y2 = lines[index].y2 + }) + + const flowLines = { + bottom: bottomTopFlowLine(surface).find((obj) => obj.target === 'bottom'), + top: bottomTopFlowLine(surface).find((obj) => obj.target === 'top'), + left: leftRightFlowLine(surface).find((obj) => obj.target === 'left'), + right: leftRightFlowLine(surface).find((obj) => obj.target === 'right'), + } + + surface.set({ flowLines: flowLines }) + }) + const moduleOptions = { fill: '#BFFD9F', stroke: 'black', strokeWidth: 0.1, - selectable: false, // 선택 가능하게 설정 + selectable: true, // 선택 가능하게 설정 lockMovementX: true, // X 축 이동 잠금 lockMovementY: true, // Y 축 이동 잠금 lockRotation: true, // 회전 잠금 lockScalingX: true, // X 축 크기 조정 잠금 lockScalingY: true, // Y 축 크기 조정 잠금 - opacity: 0.8, parentId: moduleSetupSurface.parentId, + surfaceId: moduleSetupSurface.id, name: 'module', } - let leftMargin, bottomMargin, square, chidoriLength + let leftMargin, bottomMargin, square //선택된 지붕안에 오브젝트(도머, 개구등)이 있는지 확인하는 로직 포함되면 배열 반환 const objectsIncludeSurface = (turfModuleSetupSurface) => { @@ -1927,36 +1998,6 @@ export function useModuleBasicSetting() { return containsBatchObjects } - /** - * 도머나 개구가 모듈에 걸치는지 확인하는 로직 - * @param {*} squarePolygon - * @param {*} containsBatchObjects - * @returns - */ - const checkModuleDisjointObjects = (squarePolygon, containsBatchObjects) => { - let isDisjoint = false - - if (containsBatchObjects.length > 0) { - let convertBatchObject - //도머가 있으면 적용되는 로직 - isDisjoint = containsBatchObjects.every((batchObject) => { - if (batchObject.type === 'group') { - convertBatchObject = batchObjectGroupToTurfPolygon(batchObject) - } else { - convertBatchObject = polygonToTurfPolygon(batchObject) - } - /** - * 도머가 여러개일수있으므로 겹치는게 있다면... - * 안겹치는지 확인하는 로직이라 안겹치면 true를 반환 - */ - return turf.booleanDisjoint(squarePolygon, convertBatchObject) - }) - } else { - isDisjoint = true - } - return isDisjoint - } - /** * 배치면 안에 있는지 확인 * @param {*} squarePolygon @@ -2001,6 +2042,7 @@ export function useModuleBasicSetting() { let turfCoordnates = squarePolygon.geometry.coordinates[0].slice(0, -1) let points = turfCoordnates.map((coord) => ({ x: coord[0], y: coord[1] })) + moduleOptions.surfaceId = moduleSetupSurface.id let tempModule = new QPolygon(points, { ...moduleOptions, turfPoints: squarePolygon }) canvas?.add(tempModule) moduleSetupArray.push(tempModule) @@ -2040,6 +2082,7 @@ export function useModuleBasicSetting() { let points = turfCoordnates.map((coord) => ({ x: coord[0], y: coord[1] })) // if (disjointFromTrestle && isDisjoint) { + moduleOptions.surfaceId = moduleSetupSurface.id let tempModule = new QPolygon(points, { ...moduleOptions, turfPoints: squarePolygon }) canvas?.add(tempModule) moduleSetupArray.push(tempModule) @@ -2082,6 +2125,7 @@ export function useModuleBasicSetting() { let points = turfCoordnates.map((coord) => ({ x: coord[0], y: coord[1] })) // if (disjointFromTrestle && isDisjoint) { + moduleOptions.surfaceId = moduleSetupSurface.id let tempModule = new QPolygon(points, { ...moduleOptions, turfPoints: squarePolygon }) canvas?.add(tempModule) moduleSetupArray.push(tempModule) @@ -2122,6 +2166,7 @@ export function useModuleBasicSetting() { let points = turfCoordnates.map((coord) => ({ x: coord[0], y: coord[1] })) // if (disjointFromTrestle && isDisjoint) { + moduleOptions.surfaceId = moduleSetupSurface.id let tempModule = new QPolygon(points, { ...moduleOptions, turfPoints: squarePolygon }) canvas?.add(tempModule) moduleSetupArray.push(tempModule) @@ -2151,8 +2196,8 @@ export function useModuleBasicSetting() { } const surfaceMaxLines = findSetupSurfaceMaxLines(moduleSetupSurface) - const marginWidth = 0 - const marginHeight = 0 + const marginWidth = 1 + const marginHeight = 1 canvas.renderAll() @@ -2181,75 +2226,69 @@ export function useModuleBasicSetting() { canvas?.renderAll() - //나간애들 제외하고 설치된 애들로 겹친애들 삭제 하기 - // setupedModules.forEach((module, index) => { - // if (isMaxSetup && index > 0) { - // const isOverlap = turf.booleanOverlap(polygonToTurfPolygon(setupedModules[index - 1]), polygonToTurfPolygon(module)) - // //겹치는지 확인 - // if (isOverlap) { - // //겹쳐있으면 삭제 - // // canvas?.remove(module) - // module.set({ fill: 'rgba(72, 161, 250, 0.4)', stroke: 'black', strokeWidth: 0.1 }) - // canvas.renderAll() - // setupedModules.splice(index, 1) - // return false - // } - // } - // }) + // 나간애들 제외하고 설치된 애들로 겹친애들 삭제 하기 + setupedModules.forEach((module, index) => { + if (index > 0) { + const isOverlap = turf.booleanOverlap(polygonToTurfPolygon(setupedModules[index - 1]), polygonToTurfPolygon(module)) + //겹치는지 확인 + if (isOverlap) { + //겹쳐있으면 삭제 + canvas?.remove(module) + // module.set({ fill: 'rgba(72, 161, 250, 0.4)', stroke: 'black', strokeWidth: 0.1 }) + canvas.renderAll() + setupedModules.splice(index, 1) + return false + } + } + }) moduleSetupSurface.set({ modules: setupedModules }) - // const moduleArray = [...moduleIsSetup] - // moduleArray.push({ - // surfaceId: moduleSetupSurface.surfaceId, - // moduleSetupArray: setupedModules, + // console.log('moduleSetupSurface', moduleSetupSurface) + // console.log('setupedModules', setupedModules) + + // const groupTest = new fabric.Group([moduleSetupSurface, ...setupedModules], { + // angle: compasDeg, // }) - // setModuleIsSetup(moduleArray) + + // canvas.add(groupTest) }) // console.log(calculateForApi()) + + //드래그 하기위해 기능 활성화 } - const flatRoofMakeSurface = (roof, setupSurface) => { - setupSurface.angle = -compasDeg - roof.angle = -compasDeg + /** + * 도머나 개구가 모듈에 걸치는지 확인하는 로직 + * @param {*} squarePolygon + * @param {*} containsBatchObjects + * @returns + */ + const checkModuleDisjointObjects = (squarePolygon, containsBatchObjects) => { + let isDisjoint = false - const roofPoints = roof.getCurrentPoints() - const roofLines = roofPoints.map((point, index) => { - const nextIndex = (index + 1) % roofPoints.length - const nextPoint = roofPoints[nextIndex] - - return { - x1: point.x, - y1: point.y, - x2: nextPoint.x, - y2: nextPoint.y, - } - }) - roof.set({ lines: roofLines }) - roof.fire('modified') - - const surfacePoints = setupSurface.getCurrentPoints() - const surfaceLines = surfacePoints.map((point, index) => { - const nextIndex = (index + 1) % surfacePoints.length - const nextPoint = surfacePoints[nextIndex] - - return { - x1: point.x, - y1: point.y, - x2: nextPoint.x, - y2: nextPoint.y, - } - }) - setupSurface.set({ lines: surfaceLines }) - - const flowLines = { - bottom: bottomTopFlowLine(setupSurface).find((obj) => obj.target === 'bottom'), - top: bottomTopFlowLine(setupSurface).find((obj) => obj.target === 'top'), - left: leftRightFlowLine(setupSurface).find((obj) => obj.target === 'left'), - right: leftRightFlowLine(setupSurface).find((obj) => obj.target === 'right'), + if (containsBatchObjects.length > 0) { + let convertBatchObject + //도머가 있으면 적용되는 로직 + isDisjoint = containsBatchObjects.every((batchObject) => { + if (batchObject.type === 'group') { + convertBatchObject = batchObjectGroupToTurfPolygon(batchObject) + } else { + if (!batchObject.points) { + batchObject.set({ points: rectToPolygon(batchObject) }) + } + convertBatchObject = polygonToTurfPolygon(batchObject) + } + /** + * 도머가 여러개일수있으므로 겹치는게 있다면... + * 안겹치는지 확인하는 로직이라 안겹치면 true를 반환 + */ + return turf.booleanDisjoint(squarePolygon, convertBatchObject) + }) + } else { + isDisjoint = true } - setupSurface.set({ flowLines: flowLines }) - setupSurface.fire('modified') + return isDisjoint } return { @@ -2259,5 +2298,6 @@ export function useModuleBasicSetting() { restoreModuleInstArea, manualFlatroofModuleSetup, autoFlatroofModuleSetup, + checkModuleDisjointObjects, } } diff --git a/src/hooks/object/useObjectBatch.js b/src/hooks/object/useObjectBatch.js index dd67b017..6fd858a1 100644 --- a/src/hooks/object/useObjectBatch.js +++ b/src/hooks/object/useObjectBatch.js @@ -5,13 +5,14 @@ import { useRecoilValue } from 'recoil' import { canvasState } from '@/store/canvasAtom' import { BATCH_TYPE, INPUT_TYPE } from '@/common/common' import { useEvent } from '@/hooks/useEvent' -import { pointsToTurfPolygon, polygonToTurfPolygon, rectToPolygon, setSurfaceShapePattern, triangleToPolygon } from '@/util/canvas-util' +import { pointsToTurfPolygon, polygonToTurfPolygon, rectToPolygon, triangleToPolygon } from '@/util/canvas-util' import { useSwal } from '@/hooks/useSwal' import * as turf from '@turf/turf' import { usePolygon } from '@/hooks/usePolygon' import { QPolygon } from '@/components/fabric/QPolygon' import { v4 as uuidv4 } from 'uuid' import { fontSelector } from '@/store/fontAtom' +import { useRoofFn } from '@/hooks/common/useRoofFn' export function useObjectBatch({ isHidden, setIsHidden }) { const { getMessage } = useMessage() @@ -20,6 +21,7 @@ export function useObjectBatch({ isHidden, setIsHidden }) { // const { addCanvasMouseEventListener, initEvent, addDocumentEventListener } = useContext(EventContext) const { swalFire } = useSwal() const { drawDirectionArrow } = usePolygon() + const { setSurfaceShapePattern } = useRoofFn() const lengthTextFont = useRecoilValue(fontSelector('lengthText')) useEffect(() => { diff --git a/src/hooks/option/useCanvasSetting.js b/src/hooks/option/useCanvasSetting.js index 7309d3d2..e4c02664 100644 --- a/src/hooks/option/useCanvasSetting.js +++ b/src/hooks/option/useCanvasSetting.js @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from 'react' +import { useEffect, useState, useRef } from 'react' import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil' import { adsorptionPointModeState, @@ -19,8 +19,9 @@ import { settingModalSecondOptionsState, settingModalGridOptionsState, basicSettingState, - settingsState, roofMaterialsAtom, + selectedRoofMaterialSelector, + addedRoofsState, } from '@/store/settingAtom' import { POLYGON_TYPE } from '@/common/common' import { globalFontAtom } from '@/store/fontAtom' @@ -28,6 +29,7 @@ import { dimensionLineSettingsState } from '@/store/commonUtilsAtom' import { gridColorState } from '@/store/gridAtom' import { useColor } from 'react-color-palette' import { useMasterController } from '@/hooks/common/useMasterController' +import { ROOF_MATERIAL_LAYOUT } from '@/components/floor-plan/modal/placementShape/PlacementShapeSetting' const defaultDotLineGridSetting = { INTERVAL: { @@ -41,6 +43,8 @@ const defaultDotLineGridSetting = { LINE: false, } +let previousRoofMaterialsYn = 'N'; // 지붕재 select 정보 비교 후 변경된 것이 없으면 1회만 실행 + export function useCanvasSetting() { const canvas = useRecoilValue(canvasState) // canvas가 null이 아닐 때에만 getObjects 호출 @@ -92,8 +96,9 @@ export function useCanvasSetting() { const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState) const [basicSetting, setBasicSettings] = useRecoilState(basicSettingState) - const { getRoofMaterialList } = useMasterController() + const { getRoofMaterialList, getModuleTypeItemList } = useMasterController() const [roofMaterials, setRoofMaterials] = useRecoilState(roofMaterialsAtom) + const [addedRoofs, setAddedRoofs] = useRecoilState(addedRoofsState) const SelectOptions = [ { id: 1, name: getMessage('modal.canvas.setting.grid.dot.line.setting.line.origin'), value: 1 }, @@ -102,15 +107,62 @@ export function useCanvasSetting() { { id: 4, name: '1/10', value: 1 / 10 }, ] + const selectedRoofMaterial = useRecoilValue(selectedRoofMaterialSelector) + useEffect(() => { - addRoofMaterials() + if (roofMaterials.length !== 0) { + return + } + addRoofMaterials() }, []) + //지붕재 초기세팅 const addRoofMaterials = async () => { + if (roofMaterials.length !== 0) { + return + } const { data } = await getRoofMaterialList() - setRoofMaterials(data) + + const roofLists = data.map((item, idx) => ({ + ...item, + id: item.roofMatlCd, + name: item.roofMatlNm, + selected: idx === 0, + index: idx, + nameJp: item.roofMatlNmJp, + length: item.lenBase && parseInt(item.lenBase), + width: item.widBase && parseInt(item.widBase), + raft: item.raftBase && parseInt(item.raftBase), + layout: ['ROOF_ID_SLATE', 'ROOF_ID_SINGLE'].includes(item.roofMatlCd) ? ROOF_MATERIAL_LAYOUT.STAIRS : ROOF_MATERIAL_LAYOUT.PARALLEL, + hajebichi: item.roofPchBase && parseInt(item.roofPchBase), + })) + setRoofMaterials(roofLists) + const selectedRoofMaterial = roofLists[0] + + if (addedRoofs.length === 0) { + const newAddedRoofs = [] + newAddedRoofs.push({ ...selectedRoofMaterial, selected: true, index: 0 }) + setAddedRoofs(newAddedRoofs) + } + setBasicSettings({ ...basicSetting, selectedRoofMaterial: selectedRoofMaterial }) } + const previousRoofMaterialsRef = useRef(null); + + useEffect(() => { + // 지붕재 select 정보가 존재해야 배치면초기설정 DB 정보 비교 후 지붕재 정보를 가져올 수 있음 + if (roofMaterials.length !== 0 && JSON.stringify(previousRoofMaterialsRef.current) !== JSON.stringify(roofMaterials)) { + // 지붕재 select 정보 비교 후 변경된 것이 없으면 1회만 실행 + if (roofMaterials && previousRoofMaterialsYn === 'N') { + fetchBasicSettings(); + previousRoofMaterialsYn = 'Y'; + } + + // 이전 값을 업데이트 + previousRoofMaterialsRef.current = roofMaterials; + } + }, [roofMaterials]); + useEffect(() => { if (!canvas) { return @@ -141,14 +193,6 @@ export function useCanvasSetting() { canvas?.renderAll() }, [corridorDimension]) - // 배치면 초기설정 변경 시 - useEffect(() => { - //console.log('useCanvasSetting canvasSetting 실행', canvasSetting) - if (canvasSetting.flag) { - basicSettingSave() - } - }, [canvasSetting]) - useEffect(() => { console.log('🚀 ~ useEffect ~ settingsDataSave:', settingsDataSave) if (settingsDataSave !== undefined) onClickOption2() @@ -228,59 +272,82 @@ export function useCanvasSetting() { // 기본설정(PlacementShapeSetting) 조회 및 초기화 const fetchBasicSettings = async () => { try { - await get({ url: `/api/canvas-management/canvas-basic-settings/by-object/${correntObjectNo}` }).then((res) => { - console.log('fetchBasicSettings res ', res) - if (res.length == 0) return + await get({ url: `/api/canvas-management/canvas-basic-settings/by-object/0/${correntObjectNo}` }).then((res) => { + + let roofsRow = {} + let roofsArray = {} - // 'roofs' 배열을 생성하여 각 항목을 추가 - const roofsRow = res.map((item) => { - return { - roofSizeSet: item.roofSizeSet, - roofAngleSet: item.roofAngleSet, - } - }) - - const roofsArray = res.some((item) => !item.roofSeq) - ? //최초 지붕재 추가 정보의 경우 roofsArray를 초기화 설정 - res.map(() => ({ - flag: false, + if (res.length > 0) { + roofsRow = res.map((item) => { + return { + roofSizeSet: item.roofSizeSet, + roofAngleSet: item.roofAngleSet, + } + }) + roofsArray = res.map((item) => { + return { roofApply: true, roofSeq: 1, - roofType: 1, - roofWidth: 265, - roofHeight: 235, - roofHajebichi: 0, - roofGap: 455, - // roofType: 1, - // roofWidth: 200, - // roofHeight: 200, - // roofHajebichi: 200, - // roofGap: 0, - roofLayout: 'parallel', - })) - : res.map((item) => ({ - flag: false, - roofApply: item.roofApply === '' || item.roofApply === false ? false : true, - roofSeq: item.roofSeq, - roofType: item.roofType, + roofMatlCd: item.roofMatlCd, roofWidth: item.roofWidth, roofHeight: item.roofHeight, roofHajebichi: item.roofHajebichi, roofGap: item.roofGap, roofLayout: item.roofLayout, - })) - console.log('roofsArray ', roofsArray) + } + }) + } else { + roofsRow = [ + { + roofSizeSet: 1, + roofAngleSet: 'slope', + }, + ] + + roofsArray = [ + { + roofApply: true, + roofSeq: 1, + roofMatlCd: 'ROOF_ID_WA_53A', + roofWidth: 265, + roofHeight: 235, + roofHajebichi: 0, + roofGap: 'HEI_455', + roofLayout: 'P', + }, + ] + } + // 나머지 데이터와 함께 'roofs' 배열을 patternData에 넣음 const patternData = { - roofSizeSet: roofsRow[0].roofSizeSet, // 첫 번째 항목의 값을 사용 - roofAngleSet: roofsRow[0].roofAngleSet, // 첫 번째 항목의 값을 사용 + roofSizeSet: roofsRow[0].roofSizeSet, + roofAngleSet: roofsRow[0].roofAngleSet, roofs: roofsArray, // 만들어진 roofs 배열 } - //console.error('patternData', patternData) + //console.log('fetchBasicSettings patternData', patternData) // 데이터 설정 - setBasicSettings({ ...patternData }) + const addRoofs = [] + roofMaterials?.map((material) => { + if (material.roofMatlCd === roofsArray[0].roofMatlCd) { + addRoofs.push({ ...material, selected: true + , index: 0 + , width: roofsArray[0].roofWidth + , length: roofsArray[0].roofHeight + , hajebichi: roofsArray[0].roofHajebichi + , raft: roofsArray[0].roofGap + , layout: roofsArray[0].roofLayout + }) + + setAddedRoofs(addRoofs) + setBasicSettings({ ...basicSetting, roofMaterials: addRoofs[0] + , roofSizeSet: roofsRow[0].roofSizeSet + , roofAngleSet: roofsRow[0].roofAngleSet + , roofsData: roofsArray + , selectedRoofMaterial: addRoofs[0] }) + } + }) }) } catch (error) { console.error('Data fetching error:', error) @@ -299,15 +366,29 @@ export function useCanvasSetting() { objectNo: correntObjectNo, roofSizeSet: basicSetting.roofSizeSet, roofAngleSet: basicSetting.roofAngleSet, - roofMaterialsAddList: basicSetting.roofs, // TODO : 선택된 roof로 변경해야함 + roofMaterialsAddList: [ + { + roofApply: true, + roofSeq: 0, + roofMatlCd: basicSetting.roofsData.roofMatlCd === null || basicSetting.roofsData.roofMatlCd === undefined ? 'ROOF_ID_WA_53A' : basicSetting.roofsData.roofMatlCd, + roofWidth: basicSetting.roofsData.roofWidth === null || basicSetting.roofsData.roofWidth === undefined ? 0 : basicSetting.roofsData.roofWidth, + roofHeight: basicSetting.roofsData.roofHeight === null || basicSetting.roofsData.roofHeight === undefined ? 0 : basicSetting.roofsData.roofHeight, + roofHajebichi: basicSetting.roofsData.roofHajebichi === null || basicSetting.roofsData.roofHajebichi === undefined ? 0 : basicSetting.roofsData.roofHajebichi, + roofGap: basicSetting.roofsData.roofGap === null || basicSetting.roofsData.roofGap === undefined ? 'HEI_455' : basicSetting.roofsData.roofGap, + roofLayout: basicSetting.roofsData.roofLayout === null || basicSetting.roofsData.roofLayout === undefined ? 'P' : basicSetting.roofsData.roofLayout, + }, + ], } + console.log('basicSettingSave patternData ', patternData) + await post({ url: `/api/canvas-management/canvas-basic-settings`, data: patternData }).then((res) => { swalFire({ text: getMessage(res.returnMessage) }) }) //Recoil 설정 - setCanvasSetting({ ...basicSetting, flag: false }) + setCanvasSetting({ ...basicSetting }) + fetchBasicSettings() } catch (error) { swalFire({ text: getMessage(res.returnMessage), icon: 'error' }) } @@ -681,11 +762,12 @@ export function useCanvasSetting() { setCanvasSetting, basicSetting, setBasicSettings, - fetchBasicSettings, basicSettingSave, settingsData, setSettingsData, settingsDataSave, setSettingsDataSave, + addedRoofs, + setAddedRoofs, } } diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js index ebb0184a..0671f502 100644 --- a/src/hooks/roofcover/useRoofAllocationSetting.js +++ b/src/hooks/roofcover/useRoofAllocationSetting.js @@ -1,10 +1,9 @@ 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 { useEffect, useRef, useState } from 'react' import { useSwal } from '@/hooks/useSwal' import { usePolygon } from '@/hooks/usePolygon' -import { roofDisplaySelector } from '@/store/settingAtom' +import { addedRoofsState, basicSettingState, roofDisplaySelector, roofMaterialsSelector, selectedRoofMaterialSelector } from '@/store/settingAtom' import { usePopup } from '@/hooks/usePopup' import { POLYGON_TYPE } from '@/common/common' import { v4 as uuidv4 } from 'uuid' @@ -13,6 +12,8 @@ import { useMessage } from '@/hooks/useMessage' import useMenu from '@/hooks/common/useMenu' import { useCanvasMenu } from '@/hooks/common/useCanvasMenu' import { menuTypeState } from '@/store/menuAtom' +import { useRoofFn } from '@/hooks/common/useRoofFn' +import { ROOF_MATERIAL_LAYOUT } from '@/components/floor-plan/modal/placementShape/PlacementShapeSetting' // 지붕면 할당 export function useRoofAllocationSetting(id) { @@ -26,69 +27,15 @@ export function useRoofAllocationSetting(id) { const { swalFire } = useSwal() const { setMenuNumber } = useCanvasMenu() const setMenuType = useSetRecoilState(menuTypeState) - const roofMaterials = [ - { - id: 'A', - name: '기와1', - type: 'A', - width: '200', - length: '200', - alignType: 'parallel', - }, - { - id: 'B', - name: '기와2', - type: 'B', - rafter: '200', - alignType: 'parallel', - }, - { - id: 'C', - name: '기와3', - type: 'C', - hajebichi: '200', - alignType: 'stairs', - }, - { - id: 'D', - name: '기와4', - type: 'D', - length: '200', - alignType: 'stairs', - }, - ] - const widths = [ - { name: '200', id: 'q' }, - { name: '250', id: 'q1' }, - { name: '300', id: 'q2' }, - ] - const lengths = [ - { name: '200', id: 'w' }, - { name: '250', id: 'w1' }, - { name: '300', id: 'w2' }, - ] - const rafters = [ - { name: '200', id: 'e' }, - { name: '250', id: 'e1' }, - { name: '300', id: 'e2' }, - ] - - const [values, setValues] = useState([ - { - id: 'A', - type: 'A', - roofMaterial: { name: '기와1' }, - width: { name: '200' }, - length: { name: '250' }, - rafter: { name: '300' }, - alignType: 'stairs', - }, - ]) - - const [radioValue, setRadioValue] = useState('A') + const roofMaterials = useRecoilValue(roofMaterialsSelector) + const selectedRoofMaterial = useRecoilValue(selectedRoofMaterialSelector) + const [basicSetting, setBasicSetting] = useRecoilState(basicSettingState) + const [currentRoofMaterial, setCurrentRoofMaterial] = useState(roofMaterials[0]) // 팝업 내 기준 지붕재 + const [roofList, setRoofList] = useRecoilState(addedRoofsState) // 배치면 초기설정에서 선택한 지붕재 배열 const [editingLines, setEditingLines] = useState([]) + const [currentRoofList, setCurrentRoofList] = useState(roofList) - const [selectedRoofMaterial, setSelectedRoofMaterial] = useState(roofMaterials[0]) + const { setSurfaceShapePattern } = useRoofFn() useEffect(() => { const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) // roofPolygon.innerLines @@ -130,16 +77,31 @@ export function useRoofAllocationSetting(id) { }, []) const onAddRoofMaterial = () => { - setValues([...values, selectedRoofMaterial]) + if (currentRoofList.length >= 4) { + swalFire({ type: 'alert', icon: 'error', text: getMessage('지붕재는 4개까지 선택 가능합니다.') }) + return + } + setCurrentRoofList([ + ...currentRoofList, + { + ...currentRoofMaterial, + selected: false, + id: currentRoofMaterial.roofMatlCd, + name: currentRoofMaterial.roofMatlNm, + index: currentRoofList.length, + }, + ]) } - const onDeleteRoofMaterial = (id) => { - setValues(values.filter((value) => value.id !== id)) + const onDeleteRoofMaterial = (idx) => { + const isSelected = currentRoofList[idx].selected + const newRoofList = [...currentRoofList].filter((_, index) => index !== idx) + if (isSelected) { + newRoofList[0].selected = true + } + setCurrentRoofList(newRoofList) } - const { handleMenu } = useMenu() - const [currentMenu, setCurrentMenu] = useRecoilState(currentMenuState) - // 선택한 지붕재로 할당 const handleSave = () => { // 모두 actualSize 있으면 바로 적용 없으면 actualSize 설정 @@ -150,6 +112,24 @@ export function useRoofAllocationSetting(id) { } } + // 지붕재 오른쪽 마우스 클릭 후 단일로 지붕재 변경 필요한 경우 + const handleSaveContext = () => { + const newRoofList = currentRoofList.map((roof, idx) => { + return { ...roof, index: idx } + }) + setBasicSetting((prev) => { + return { + ...prev, + selectedRoofMaterial: newRoofList.find((roof) => roof.selected), + } + }) + + setRoofList(newRoofList) + const selectedRoofMaterial = newRoofList.find((roof) => roof.selected) + setSurfaceShapePattern(currentObject, roofDisplay.column, false, selectedRoofMaterial) + closeAll() + } + const handleAlloc = () => { if (!checkInnerLines()) { apply() @@ -167,16 +147,18 @@ export function useRoofAllocationSetting(id) { let result = false roofBases.forEach((roof) => { - roof.innerLines.forEach((line) => { - if (!line.attributes.actualSize || line.attributes?.actualSize === 0) { - line.set({ - strokeWidth: 4, - stroke: 'black', - selectable: true, - }) - result = true - } - }) + if (roof.separatePolygon.length === 0) { + roof.innerLines.forEach((line) => { + if (!line.attributes.actualSize || line.attributes?.actualSize === 0) { + line.set({ + strokeWidth: 4, + stroke: 'black', + selectable: true, + }) + result = true + } + }) + } }) if (result) canvas?.renderAll() @@ -184,7 +166,7 @@ export function useRoofAllocationSetting(id) { } const apply = () => { - const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF && !obj.isFixed) + const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) roofBases.forEach((roofBase) => { try { @@ -207,6 +189,19 @@ export function useRoofAllocationSetting(id) { canvas.remove(wallLine) }) + const newRoofList = currentRoofList.map((roof, idx) => { + return { ...roof, index: idx } + }) + + setBasicSetting((prev) => { + return { + ...prev, + selectedRoofMaterial: newRoofList.find((roof) => roof.selected), + } + }) + + setRoofList(newRoofList) + const roofs = canvas.getObjects().filter((obj) => obj.name === 'roof') roofs.forEach((roof) => { @@ -214,7 +209,12 @@ export function useRoofAllocationSetting(id) { roof.set({ isFixed: true, }) - setSurfaceShapePattern(roof, roofDisplay.column) + setSurfaceShapePattern( + roof, + roofDisplay.column, + false, + currentRoofList.find((roof) => roof.selected), + ) drawDirectionArrow(roof) }) @@ -246,25 +246,72 @@ export function useRoofAllocationSetting(id) { canvas?.renderAll() } - const handleRadioOnChange = (e) => { - setRadioValue(e.target) + // 지붕재 변경 + const handleChangeRoofMaterial = (value, index) => { + const selectedIndex = roofMaterials.findIndex((roof) => roof.selected) + + const selectedRoofMaterial = roofMaterials.find((roof) => roof.roofMatlCd === value.id) + const newRoofList = currentRoofList.map((roof, idx) => { + if (idx === index) { + return { ...selectedRoofMaterial } + } + return roof + }) + + setCurrentRoofList(newRoofList) + } + + // 기본 지붕재 radio값 변경 + const handleDefaultRoofMaterial = (index) => { + const newRoofList = currentRoofList.map((roof, idx) => { + return { ...roof, selected: idx === index } + }) + + setCurrentRoofList(newRoofList) + } + + // 서까래 변경 + const handleChangeRaft = (e, index) => { + const raftValue = e.clCode + + const newRoofList = currentRoofList.map((roof, idx) => { + if (idx === index) { + return { ...roof, raft: raftValue } + } + return roof + }) + + setCurrentRoofList(newRoofList) + } + + const handleChangeLayout = (layoutValue, index) => { + const newRoofList = currentRoofList.map((roof, idx) => { + if (idx === index) { + return { ...roof, layout: layoutValue } + } + return roof + }) + + setCurrentRoofList(newRoofList) } return { handleSave, onAddRoofMaterial, onDeleteRoofMaterial, - handleRadioOnChange, handleAlloc, setLineSize, - widths, - lengths, - rafters, - values, roofMaterials, selectedRoofMaterial, - setSelectedRoofMaterial, - radioValue, - setRadioValue, + basicSetting, + setBasicSetting, + currentRoofMaterial, + setCurrentRoofMaterial, + handleDefaultRoofMaterial, + handleChangeRoofMaterial, + handleChangeRaft, + handleChangeLayout, + handleSaveContext, + currentRoofList, } } diff --git a/src/hooks/roofcover/useRoofShapePassivitySetting.js b/src/hooks/roofcover/useRoofShapePassivitySetting.js index a2c799f4..7c29dd8d 100644 --- a/src/hooks/roofcover/useRoofShapePassivitySetting.js +++ b/src/hooks/roofcover/useRoofShapePassivitySetting.js @@ -86,12 +86,24 @@ export function useRoofShapePassivitySetting(id) { useEffect(() => { const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + let stroke, strokeWidth lines.forEach((line) => { + if (line.attributes?.type === LINE_TYPE.WALLLINE.EAVES || line.attributes?.type === LINE_TYPE.WALLLINE.HIPANDGABLE) { + stroke = '#45CD7D' + strokeWidth = 4 + } else if (line.attributes?.type === LINE_TYPE.WALLLINE.GABLE || line.attributes?.type === LINE_TYPE.WALLLINE.JERKINHEAD) { + stroke = '#3FBAE6' + strokeWidth = 4 + } else { + stroke = '#000000' + strokeWidth = 4 + } line.set({ - stroke: '#000000', - strokeWidth: 4, + stroke, + strokeWidth, }) }) + canvas.renderAll() if (!currentObject) { return } @@ -105,7 +117,6 @@ export function useRoofShapePassivitySetting(id) { }) currentLineRef.current = currentObject - canvas.renderAll() }, [currentObject]) @@ -125,6 +136,10 @@ export function useRoofShapePassivitySetting(id) { const index = lines.findIndex((line) => line === selectedLine) const nextLine = lines[index + 1] || lines[0] + if (nextLine.attributes?.isFixed) { + canvas.discardActiveObject() + return + } canvas.setActiveObject(nextLine) } @@ -155,7 +170,7 @@ export function useRoofShapePassivitySetting(id) { } currentLineRef.current.set({ - attributes, + attributes: { ...attributes, isFixed: true }, }) history.current.push(currentLineRef.current) diff --git a/src/hooks/roofcover/useRoofShapeSetting.js b/src/hooks/roofcover/useRoofShapeSetting.js index c26c61ec..82dbf53f 100644 --- a/src/hooks/roofcover/useRoofShapeSetting.js +++ b/src/hooks/roofcover/useRoofShapeSetting.js @@ -185,9 +185,20 @@ export function useRoofShapeSetting(id) { } case 4: { outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') - outerLines.forEach((line) => { - // hideLine(line) - }) + const pitch = outerLines.find((line) => line.attributes.type === LINE_TYPE.WALLLINE.SHED)?.attributes.pitch + // 변별로 설정중 한쪽흐름일 경우 한쪽흐름의 pitch로 설정 + if (pitch) { + outerLines.forEach((line) => { + if (line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { + line.attributes = { + ...line.attributes, + pitch: pitch, + onlyOffset: true, + } + } + }) + } + break } @@ -205,6 +216,7 @@ export function useRoofShapeSetting(id) { offset: eavesOffset / 10, pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, + onlyOffset: true, } } @@ -221,6 +233,7 @@ export function useRoofShapeSetting(id) { offset: eavesOffset / 10, pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, + onlyOffset: true, } } @@ -251,6 +264,7 @@ export function useRoofShapeSetting(id) { offset: eavesOffset / 10, pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, + onlyOffset: true, } } @@ -267,6 +281,7 @@ export function useRoofShapeSetting(id) { offset: eavesOffset / 10, pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, + onlyOffset: true, } } @@ -296,6 +311,7 @@ export function useRoofShapeSetting(id) { offset: eavesOffset / 10, pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, + onlyOffset: true, } } @@ -312,6 +328,7 @@ export function useRoofShapeSetting(id) { offset: eavesOffset / 10, pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, + onlyOffset: true, } } @@ -342,6 +359,7 @@ export function useRoofShapeSetting(id) { offset: eavesOffset / 10, pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, + onlyOffset: true, } } @@ -358,6 +376,7 @@ export function useRoofShapeSetting(id) { offset: eavesOffset / 10, pitch: pitchRef.current, type: LINE_TYPE.WALLLINE.EAVES, + onlyOffset: true, } } @@ -595,17 +614,23 @@ export function useRoofShapeSetting(id) { break } } - selectedLine.attributes = attributes - history.current.push(selectedLine) + selectedLine.attributes = { ...attributes, isFixed: true } + canvas.renderAll() nextLineFocus(selectedLine) } const nextLineFocus = (selectedLine) => { const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + const index = lines.findIndex((line) => line.idx === selectedLine.idx) const nextLine = lines[index + 1] || lines[0] + if (nextLine.attributes.isFixed) { + canvas.discardActiveObject() + return + } + history.current.push(selectedLine) canvas.setActiveObject(nextLine) } diff --git a/src/hooks/surface/usePlacementShapeDrawing.js b/src/hooks/surface/usePlacementShapeDrawing.js index 95388ca4..92f1e06b 100644 --- a/src/hooks/surface/usePlacementShapeDrawing.js +++ b/src/hooks/surface/usePlacementShapeDrawing.js @@ -13,7 +13,7 @@ import { useMouse } from '@/hooks/useMouse' import { useLine } from '@/hooks/useLine' import { useTempGrid } from '@/hooks/useTempGrid' import { useEffect, useRef } from 'react' -import { distanceBetweenPoints, setSurfaceShapePattern } from '@/util/canvas-util' +import { distanceBetweenPoints } from '@/util/canvas-util' import { fabric } from 'fabric' import { calculateAngle } from '@/util/qpolygon-utils' import { @@ -33,6 +33,7 @@ import { POLYGON_TYPE } from '@/common/common' import { usePopup } from '@/hooks/usePopup' import { roofDisplaySelector } from '@/store/settingAtom' +import { useRoofFn } from '@/hooks/common/useRoofFn' // 면형상 배치 export function usePlacementShapeDrawing(id) { @@ -46,6 +47,7 @@ export function usePlacementShapeDrawing(id) { const { addLine, removeLine } = useLine() const { addPolygonByLines, drawDirectionArrow } = usePolygon() const { tempGridMode } = useTempGrid() + const { setSurfaceShapePattern } = useRoofFn() const verticalHorizontalMode = useRecoilValue(verticalHorizontalModeState) const adsorptionPointAddMode = useRecoilValue(adsorptionPointAddModeState) diff --git a/src/hooks/surface/useSurfaceShapeBatch.js b/src/hooks/surface/useSurfaceShapeBatch.js index 844829d3..99eed453 100644 --- a/src/hooks/surface/useSurfaceShapeBatch.js +++ b/src/hooks/surface/useSurfaceShapeBatch.js @@ -3,7 +3,7 @@ import { useRecoilValue } from 'recoil' import { canvasState, globalPitchState } from '@/store/canvasAtom' import { MENU, POLYGON_TYPE } from '@/common/common' -import { getIntersectionPoint, setSurfaceShapePattern } from '@/util/canvas-util' +import { getIntersectionPoint } from '@/util/canvas-util' import { degreesToRadians } from '@turf/turf' import { QPolygon } from '@/components/fabric/QPolygon' import { useSwal } from '@/hooks/useSwal' @@ -15,6 +15,7 @@ import { usePolygon } from '@/hooks/usePolygon' import { fontSelector } from '@/store/fontAtom' import { slopeSelector } from '@/store/commonAtom' import { QLine } from '@/components/fabric/QLine' +import { useRoofFn } from '@/hooks/common/useRoofFn' export function useSurfaceShapeBatch() { const { getMessage } = useMessage() @@ -29,6 +30,7 @@ export function useSurfaceShapeBatch() { const { addCanvasMouseEventListener, initEvent } = useEvent() // const { addCanvasMouseEventListener, initEvent } = useContext(EventContext) const { closePopup } = usePopup() + const { setSurfaceShapePattern } = useRoofFn() const applySurfaceShape = (surfaceRefs, selectedType, id) => { let length1, length2, length3, length4, length5 diff --git a/src/hooks/useAdsorptionPoint.js b/src/hooks/useAdsorptionPoint.js index 4bdef964..12510dcf 100644 --- a/src/hooks/useAdsorptionPoint.js +++ b/src/hooks/useAdsorptionPoint.js @@ -36,11 +36,20 @@ export function useAdsorptionPoint() { canvas.renderAll() } + const removeAdsorptionPoint = () => { + const adsorptionPoints = getAdsorptionPoints() + adsorptionPoints.forEach((adsorptionPoint) => { + canvas.remove(adsorptionPoint) + }) + canvas.renderAll() + } + return { adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, getAdsorptionPoints, adsorptionPointAddModeStateEvent, + removeAdsorptionPoint, } } diff --git a/src/hooks/useCanvasEvent.js b/src/hooks/useCanvasEvent.js index 0e8cd64b..7ea3e03c 100644 --- a/src/hooks/useCanvasEvent.js +++ b/src/hooks/useCanvasEvent.js @@ -3,7 +3,6 @@ import { useRecoilState, useRecoilValue } from 'recoil' import { v4 as uuidv4 } from 'uuid' import { canvasSizeState, canvasState, canvasZoomState, currentObjectState } from '@/store/canvasAtom' import { QPolygon } from '@/components/fabric/QPolygon' -import { usePlan } from '@/hooks/usePlan' import { fontSelector } from '@/store/fontAtom' // 캔버스에 필요한 이벤트 @@ -14,7 +13,6 @@ export function useCanvasEvent() { const canvasSize = useRecoilValue(canvasSizeState) const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState) const lengthTextOption = useRecoilValue(fontSelector('lengthText')) - const { modifiedPlanFlag, setModifiedPlanFlag } = usePlan() useEffect(() => { canvas?.setZoom(canvasZoom / 100) @@ -40,10 +38,6 @@ export function useCanvasEvent() { onChange: (e) => { const target = e.target - if (target.name !== 'mouseLine' && !modifiedPlanFlag) { - setModifiedPlanFlag((prev) => !prev) - } - if (target) { // settleDown(target) } @@ -58,10 +52,6 @@ export function useCanvasEvent() { target.uuid = uuidv4() } - if (target.name !== 'mouseLine' && !modifiedPlanFlag) { - setModifiedPlanFlag((prev) => !prev) - } - if (target.type === 'QPolygon' || target.type === 'QLine') { const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'lengthText') textObjs.forEach((obj) => { @@ -164,9 +154,6 @@ export function useCanvasEvent() { target.on('moving', (e) => { target.uuid = uuidv4() - if (!modifiedPlanFlag) { - setModifiedPlanFlag((prev) => !prev) - } if (target.parentDirection === 'left' || target.parentDirection === 'right') { const minX = target.minX diff --git a/src/hooks/useContextMenu.js b/src/hooks/useContextMenu.js index cbf326e8..837c489a 100644 --- a/src/hooks/useContextMenu.js +++ b/src/hooks/useContextMenu.js @@ -1,7 +1,7 @@ import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' import { canvasState, currentMenuState, currentObjectState } from '@/store/canvasAtom' import { useEffect, useState } from 'react' -import { MENU } from '@/common/common' +import { MENU, POLYGON_TYPE } from '@/common/common' import AuxiliarySize from '@/components/floor-plan/modal/auxiliary/AuxiliarySize' import { usePopup } from '@/hooks/usePopup' import { v4 as uuidv4 } from 'uuid' @@ -23,7 +23,7 @@ import { useCommonUtils } from './common/useCommonUtils' import { useMessage } from '@/hooks/useMessage' import { useCanvasEvent } from '@/hooks/useCanvasEvent' import { contextMenuListState, contextMenuState } from '@/store/contextMenu' -import PanelEdit from '@/components/floor-plan/modal/module/PanelEdit' +import PanelEdit, { PANEL_EDIT_TYPE } 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' import ColumnInsert from '@/components/floor-plan/modal/module/column/ColumnInsert' @@ -35,6 +35,12 @@ import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch' import { fontSelector, globalFontAtom } from '@/store/fontAtom' import { useLine } from '@/hooks/useLine' import { useSwal } from '@/hooks/useSwal' +import ContextRoofAllocationSetting from '@/components/floor-plan/modal/roofAllocation/ContextRoofAllocationSetting' +import { useCanvasSetting } from './option/useCanvasSetting' +import { useGrid } from './common/useGrid' +import { useAdsorptionPoint } from './useAdsorptionPoint' +import { useRoofFn } from '@/hooks/common/useRoofFn' +import { MODULE_ALIGN_TYPE, useModule } from './module/useModule' export function useContextMenu() { const canvas = useRecoilValue(canvasState) @@ -56,8 +62,13 @@ export function useContextMenu() { const { moveSurfaceShapeBatch } = useSurfaceShapeBatch() const [globalFont, setGlobalFont] = useRecoilState(globalFontAtom) const { addLine, removeLine } = useLine() + const { removeGrid } = useGrid() + const { removeAdsorptionPoint } = useAdsorptionPoint() const commonTextFont = useRecoilValue(fontSelector('commonText')) + const { settingsData, setSettingsDataSave } = useCanvasSetting() const { swalFire } = useSwal() + const { alignModule } = useModule() + const { removeRoofMaterial, removeAllRoofMaterial } = useRoofFn() const currentMenuSetting = () => { switch (currentMenu) { @@ -77,7 +88,15 @@ export function useContextMenu() { { id: 'gridColorEdit', name: getMessage('modal.grid.color.edit'), - component: , + component: ( + + ), }, { id: 'remove', @@ -86,6 +105,10 @@ export function useContextMenu() { { id: 'removeAll', name: getMessage('delete.all'), + fn: () => { + removeGrid() + removeAdsorptionPoint() + }, }, ], ]) @@ -105,14 +128,17 @@ export function useContextMenu() { { id: 'roofMaterialPlacement', name: getMessage('contextmenu.roof.material.placement'), + component: , }, { id: 'roofMaterialRemove', name: getMessage('contextmenu.roof.material.remove'), + fn: () => removeRoofMaterial(), }, { id: 'roofMaterialRemoveAll', name: getMessage('contextmenu.roof.material.remove.all'), + fn: () => removeAllRoofMaterial(), }, { id: 'selectMove', @@ -377,7 +403,7 @@ export function useContextMenu() { { id: 'roofMaterialEdit', name: getMessage('contextmenu.roof.material.edit'), - component: , + component: , }, { id: 'linePropertyEdit', @@ -516,7 +542,15 @@ export function useContextMenu() { { id: 'gridColorEdit', name: getMessage('contextmenu.grid.color.edit'), - component: , + component: ( + + ), }, { id: 'remove', @@ -530,12 +564,8 @@ export function useContextMenu() { id: 'removeAll', name: getMessage('contextmenu.remove.all'), fn: () => { - canvas - .getObjects() - .filter((obj) => ['tempGrid', 'lineGrid', 'dotGrid'].includes(obj.name)) - .forEach((grid) => { - canvas.remove(grid) - }) + removeGrid() + removeAdsorptionPoint() canvas.discardActiveObject() }, }, @@ -597,34 +627,35 @@ export function useContextMenu() { ], ]) break - case 'panel': + case 'module': setContextMenu([ [ { id: 'remove', name: getMessage('contextmenu.remove'), + fn: () => deleteObject(), }, { id: 'move', name: getMessage('contextmenu.move'), - component: , + component: , }, { id: 'copy', name: getMessage('contextmenu.copy'), - component: , + component: , }, ], [ { id: 'columnMove', name: getMessage('contextmenu.column.move'), - component: , + component: , }, { id: 'columnCopy', name: getMessage('contextmenu.column.copy'), - component: , + component: , }, { id: 'columnRemove', @@ -641,12 +672,12 @@ export function useContextMenu() { { id: 'rowMove', name: getMessage('contextmenu.row.move'), - component: , + component: , }, { id: 'rowCopy', name: getMessage('contextmenu.row.copy'), - component: , + component: , }, { id: 'rowRemove', @@ -661,37 +692,39 @@ export function useContextMenu() { ], ]) break - case 'module': - case 'dimensionLineText': + case 'moduleSetupSurface': + case 'roof': setContextMenu([ [ { id: 'moduleVerticalCenterAlign', name: getMessage('contextmenu.module.vertical.align'), + fn: () => alignModule(MODULE_ALIGN_TYPE.VERTICAL), }, { id: 'moduleHorizonCenterAlign', name: getMessage('contextmenu.module.horizon.align'), - }, - { - id: 'moduleLeftAlign', - name: getMessage('contextmenu.module.left.align'), - }, - { - id: 'moduleRightAlign', - name: getMessage('contextmenu.module.right.align'), - }, - { - id: 'moduleUpAlign', - name: getMessage('contextmenu.module.up.align'), - }, - { - id: 'moduleDownAlign', - name: getMessage('contextmenu.module.down.align'), + fn: () => alignModule(MODULE_ALIGN_TYPE.HORIZONTAL), }, { id: 'moduleRemove', name: getMessage('contextmenu.module.remove'), + fn: () => { + const moduleSetupSurface = canvas.getObjects().filter((obj) => canvas.getActiveObjects()[0].id === obj.id)[0] + const modules = canvas.getObjects().filter((obj) => obj.surfaceId === moduleSetupSurface.id && obj.name === POLYGON_TYPE.MODULE) + canvas.remove(...modules) + canvas.renderAll() + }, + }, + { + id: 'moduleMove', + name: getMessage('contextmenu.module.move'), + component: , + }, + { + id: 'moduleCopy', + name: getMessage('contextmenu.module.copy'), + component: , }, { id: 'moduleCircuitNumberEdit', diff --git a/src/hooks/useLine.js b/src/hooks/useLine.js index 7bf01a47..5e0978ed 100644 --- a/src/hooks/useLine.js +++ b/src/hooks/useLine.js @@ -86,13 +86,14 @@ export const useLine = () => { const addPitchText = (line) => { removePitchText(line) const { startPoint, endPoint, direction, attributes } = line + const { offset, onlyOffset = false } = attributes let left, top const textStr = currentAngleType === ANGLE_TYPE.SLOPE - ? `${attributes.offset ? attributes.offset * 10 : attributes.width * 10}${attributes.pitch ? '-∠' + attributes.pitch + angleUnit : ''}` - : `${attributes.offset ? attributes.offset * 10 : attributes.width * 10}${attributes.pitch ? '-∠' + getDegreeByChon(attributes.pitch) + angleUnit : ''}` + ? `${attributes.offset ? attributes.offset * 10 : attributes.width * 10}${onlyOffset && attributes.pitch ? '-∠' + attributes.pitch + angleUnit : ''}` + : `${attributes.offset ? attributes.offset * 10 : attributes.width * 10}${onlyOffset && attributes.pitch ? '-∠' + getDegreeByChon(attributes.pitch) + angleUnit : ''}` if (direction === 'top') { left = (startPoint.x + endPoint.x) / 2 diff --git a/src/hooks/usePlan.js b/src/hooks/usePlan.js index 532759a4..b49d1eca 100644 --- a/src/hooks/usePlan.js +++ b/src/hooks/usePlan.js @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react' import { useRecoilState } from 'recoil' import { v4 as uuidv4 } from 'uuid' -import { canvasState, currentCanvasPlanState, plansState, modifiedPlansState, modifiedPlanFlagState } from '@/store/canvasAtom' +import { canvasState, currentCanvasPlanState, plansState } from '@/store/canvasAtom' import { useAxios } from '@/hooks/useAxios' import { useMessage } from '@/hooks/useMessage' import { useSwal } from '@/hooks/useSwal' @@ -12,14 +12,11 @@ import { useCanvas } from '@/hooks/useCanvas' export function usePlan() { const [planNum, setPlanNum] = useState(0) const [selectedPlan, setSelectedPlan] = useState(null) - const [currentCanvasStatus, setCurrentCanvasStatus] = useState(null) const [canvas, setCanvas] = useRecoilState(canvasState) const [currentCanvasPlan, setCurrentCanvasPlan] = useRecoilState(currentCanvasPlanState) const [plans, setPlans] = useRecoilState(plansState) // 전체 plan - const [modifiedPlans, setModifiedPlans] = useRecoilState(modifiedPlansState) // 변경된 canvas plan - const [modifiedPlanFlag, setModifiedPlanFlag] = useRecoilState(modifiedPlanFlagState) // 캔버스 실시간 오브젝트 이벤트 감지 flag const { swalFire } = useSwal() const { getMessage } = useMessage() @@ -95,64 +92,6 @@ export function usePlan() { return addCanvas() } - /** - * 캔버스에서 발생하는 실시간 오브젝트 이벤트를 감지하여 수정 여부를 확인 후 관리 - */ - const checkCanvasObjectEvent = (planId) => { - setCurrentCanvasStatus(currentCanvasData()) - if (!modifiedPlans.some((modifiedPlan) => modifiedPlan === planId) && checkModifiedCanvasPlan(planId)) { - setModifiedPlans((prev) => [...prev, planId]) - setModifiedPlanFlag(false) - } - } - useEffect(() => { - if (currentCanvasStatus) { - setCurrentCanvasPlan((prev) => ({ ...prev, canvasStatus: currentCanvasStatus })) - } - }, [currentCanvasStatus]) - - /** - * 현재 캔버스 상태와 DB에 저장된 캔버스 상태를 비교하여 수정 여부를 판단 - */ - const checkModifiedCanvasPlan = (planId) => { - const planData = plans.find((plan) => plan.id === planId) - if (planData.canvasStatus === '') { - // 빈 상태로 저장된 캔버스 - return true - } - - // 각각 object들의 uuid 목록을 추출하여 비교 - const canvasStatus = currentCanvasData() - const canvasObjsUuids = getObjectUuids(JSON.parse(canvasStatus).objects) - const dbObjsUuids = getObjectUuids(JSON.parse(planData.canvasStatus).objects) - return canvasObjsUuids.length !== dbObjsUuids.length || !canvasObjsUuids.every((uuid, index) => uuid === dbObjsUuids[index]) - } - const getObjectUuids = (objects) => { - return objects - .filter((obj) => obj.hasOwnProperty('uuid')) - .map((obj) => obj.uuid) - .sort() - } - - const resetModifiedPlans = () => { - setModifiedPlans([]) - setModifiedPlanFlag(false) - } - - /** - * 캔버스에 저장되지 않은 변경사항이 있을때 저장 여부를 확인 후 저장 - */ - const checkUnsavedCanvasPlan = async () => { - swalFire({ - text: `Plan ${currentCanvasPlan.ordering} ` + getMessage('plan.message.confirm.save.modified'), - type: 'confirm', - confirmFn: async () => { - await putCanvasStatus(currentCanvasPlan.canvasStatus) - }, - }) - resetModifiedPlans() - } - /** * DB에 저장된 데이터를 canvas에서 사용할 수 있도록 포맷화 */ @@ -206,8 +145,8 @@ export function usePlan() { } await promisePost({ url: '/api/canvas-management/canvas-statuses', data: planData }) .then((res) => { - setPlans([...plans, { id: res.data, objectNo: objectNo, userId: userId, canvasStatus: canvasStatus, ordering: planNum + 1 }]) - handleCurrentPlan(res.data) + setPlans((plans) => [...plans, { id: res.data, objectNo: objectNo, userId: userId, canvasStatus: canvasStatus, ordering: planNum + 1 }]) + updateCurrentPlan(res.data) setPlanNum(planNum + 1) }) .catch((error) => { @@ -228,7 +167,6 @@ export function usePlan() { await promisePut({ url: '/api/canvas-management/canvas-statuses', data: planData }) .then((res) => { setPlans((plans) => plans.map((plan) => (plan.id === currentCanvasPlan.id ? { ...plan, canvasStatus: canvasStatus } : plan))) - setModifiedPlans((modifiedPlans) => modifiedPlans.filter((planId) => planId !== currentCanvasPlan.id)) }) .catch((error) => { swalFire({ text: error.message, icon: 'error' }) @@ -251,13 +189,11 @@ export function usePlan() { /** * plan 이동 - * 현재 plan의 작업상태를 확인, 저장 후 이동 + * 현재 plan의 작업상태를 저장 후 이동 */ const handleCurrentPlan = async (newCurrentId) => { if (!currentCanvasPlan || currentCanvasPlan.id !== newCurrentId) { - if (currentCanvasPlan?.id && modifiedPlans.some((modifiedPlan) => modifiedPlan === currentCanvasPlan.id)) { - await saveCanvas() - } + await saveCanvas() updateCurrentPlan(newCurrentId) } } @@ -281,9 +217,12 @@ export function usePlan() { /** * 새로운 plan 생성 - * 현재 plan의 데이터가 있을 경우 복제 여부를 확인 + * 현재 plan의 데이터가 있을 경우 현재 plan 저장 및 복제 여부를 확인 */ const handleAddPlan = async (userId, objectNo) => { + if (currentCanvasPlan?.id) { + await saveCanvas() + } JSON.parse(currentCanvasData()).objects.length > 0 ? swalFire({ text: `Plan ${currentCanvasPlan.ordering} ` + getMessage('plan.message.confirm.copy'), @@ -324,7 +263,6 @@ export function usePlan() { await delCanvasById(id) .then((res) => { setPlans((plans) => plans.filter((plan) => plan.id !== id)) - setModifiedPlans((modifiedPlans) => modifiedPlans.filter((planId) => planId !== currentCanvasPlan.id)) removeImage(currentCanvasPlan.id) swalFire({ text: getMessage('plan.message.delete') }) }) @@ -362,14 +300,6 @@ export function usePlan() { canvas, plans, selectedPlan, - currentCanvasPlan, - setCurrentCanvasPlan, - modifiedPlans, - modifiedPlanFlag, - setModifiedPlanFlag, - checkCanvasObjectEvent, - checkUnsavedCanvasPlan, - resetModifiedPlans, saveCanvas, handleCurrentPlan, handleAddPlan, diff --git a/src/hooks/usePopup.js b/src/hooks/usePopup.js index 5ef59bdc..726ab353 100644 --- a/src/hooks/usePopup.js +++ b/src/hooks/usePopup.js @@ -1,10 +1,21 @@ import { useRecoilState } from 'recoil' import { contextPopupState, popupState } from '@/store/popupAtom' +/** + * 팝업 관리 훅 + * @returns + */ export function usePopup() { const [popup, setPopup] = useRecoilState(popupState) const [contextMenuPopup, setContextMenuPopup] = useRecoilState(contextPopupState) + /** + * 팝업 추가 + * @param {*} id 팝업 아이디 + * @param {*} depth 팝업 깊이 + * @param {*} component 팝업 컴포넌트 + * @param {*} isConfig 팝업 타입 + */ const addPopup = (id, depth, component, isConfig = false) => { setPopup({ config: isConfig ? [...filterDepth(depth, isConfig), { id, depth, component, isConfig }] : [...popup.config], @@ -12,6 +23,11 @@ export function usePopup() { }) } + /** + * 팝업 닫기 + * @param {*} id 팝업 아이디 + * @param {*} isConfig 팝업 타입 + */ const closePopup = (id, isConfig = false) => { if (contextMenuPopup) setContextMenuPopup(null) if (isConfig) { @@ -27,6 +43,10 @@ export function usePopup() { } } + /** + * 팝업 필터 + * @param {*} depth 팝업 깊이 + */ const filterPopup = (depth) => { setPopup({ config: [...filterDepth(depth)], @@ -34,6 +54,12 @@ export function usePopup() { }) } + /** + * 팝업 자식 필터 + * @param {*} id 팝업 아이디 + * @param {*} isConfig 팝업 타입 + * @returns + */ const filterChildrenPopup = (id, isConfig) => { let target = [] if (isConfig) { @@ -57,6 +83,10 @@ export function usePopup() { } } + /** + * 팝업 여러개 닫기 + * @param {*} ids 팝업 아이디 배열 + */ const closePopups = (ids) => { setPopup({ config: [...popup?.config.filter((child) => !ids.includes(child.id))], @@ -64,6 +94,9 @@ export function usePopup() { }) } + /** + * 팝업 전체 닫기 + */ const closeAll = () => { setPopup({ other: [], @@ -71,6 +104,9 @@ export function usePopup() { }) } + /** + * 이전 팝업 닫기 + */ const closePrevPopup = () => { setPopup({ config: [...popup?.slice(popup?.length - 1)], @@ -78,6 +114,12 @@ export function usePopup() { }) } + /** + * 팝업 깊이 필터 + * @param {*} depth 팝업 깊이 + * @param {*} isConfig 팝업 타입 + * @returns + */ const filterDepth = (depth, isConfig) => { if (isConfig) { return [...popup?.config.filter((child) => child.depth < depth)] diff --git a/src/hooks/useSwal.js b/src/hooks/useSwal.js index cb18e6a7..018d8f32 100644 --- a/src/hooks/useSwal.js +++ b/src/hooks/useSwal.js @@ -21,6 +21,8 @@ export const useSwal = () => { text, icon: icon === '' ? 'success' : icon, confirmButtonText: '확인', + }).then(() => { + confirmFn() }) } else if (type === 'confirm') { MySwal.fire({ diff --git a/src/lib/authActions.js b/src/lib/authActions.js index 0834833e..929d3895 100644 --- a/src/lib/authActions.js +++ b/src/lib/authActions.js @@ -59,33 +59,36 @@ export async function setSession(data) { await session.save() } -export async function login(formData) { +export async function login() { const session = await getSession() - - const userId = formData.get('id') - const password = formData.get('password') - - console.log('id:', userId) - console.log('password:', password) - - // const loginUser = await getUserByIdAndPassword({ userId, password }) - const loginUser = { - id: 1, - userId: 'test123', - name: 'jinsoo Kim', - email: 'jinsoo.kim@example.com', + if (session) { + redirect('/') } - if (!loginUser) { - throw Error('Wrong Credentials!') - } + // const userId = formData.get('id') + // const password = formData.get('password') - session.name = loginUser.name - session.userId = loginUser.userId - session.email = loginUser.email - session.isLoggedIn = true - console.log('session:', session) + // console.log('id:', userId) + // console.log('password:', password) - await session.save() - redirect('/') + // // const loginUser = await getUserByIdAndPassword({ userId, password }) + // const loginUser = { + // id: 1, + // userId: 'test123', + // name: 'jinsoo Kim', + // email: 'jinsoo.kim@example.com', + // } + + // if (!loginUser) { + // throw Error('Wrong Credentials!') + // } + + // session.name = loginUser.name + // session.userId = loginUser.userId + // session.email = loginUser.email + // session.isLoggedIn = true + // console.log('session:', session) + + // await session.save() + // redirect('/') } diff --git a/src/lib/session.js b/src/lib/session.js index bcedecf4..ff5a2078 100644 --- a/src/lib/session.js +++ b/src/lib/session.js @@ -3,8 +3,8 @@ export const defaultSession = {} export const sessionOptions = { password: process.env.SESSION_SECRET, cookieName: 'lama-session', - // cookieOptions: { - // httpOnly: true, - // secure: process.env.NODE_ENV === 'production', - // }, + cookieOptions: { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + }, } diff --git a/src/locales/ja.json b/src/locales/ja.json index 4112f806..a4d8c6ad 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -157,6 +157,7 @@ "plan.menu.estimate": "見積", "plan.menu.estimate.roof.alloc": "屋根面の割り当て", "modal.roof.alloc.info": "※配置面初期設定で保存した[基本屋根材]を変更したり、屋根材を追加して割り当てることができます。", + "modal.roof.alloc.default.roof.material": "基本屋根材", "modal.roof.alloc.select.roof.material": "屋根材の選択", "modal.roof.alloc.select.parallel": "並列式", "modal.roof.alloc.select.stairs": "カスケード", @@ -296,7 +297,6 @@ "modal.actual.size.setting.plane.size.length": "廊下の寸法の長さ", "modal.actual.size.setting.actual.size.length": "実寸長", "plan.message.confirm.save": "PLAN을 저장하시겠습니까?", - "plan.message.confirm.save.modified": "PLAN의 변경사항을 저장하시겠습니까?", "plan.message.confirm.copy": "PLAN을 복사하시겠습니까?", "plan.message.confirm.delete": "PLAN을 삭제하시겠습니까?", "plan.message.save": "저장되었습니다.", @@ -514,6 +514,8 @@ "color.darkblue": "남색(JA)", "site.name": "Q.CAST III", "site.sub_name": "太陽光発電システム図面管理サイト", + "site.header.link1": "選択してください。", + "site.header.link2": "オンライン保証システム", "board.notice.title": "お知らせ", "board.notice.sub.title": "お知らせ一覧", "board.faq.title": "FAQ", @@ -630,7 +632,7 @@ "stuff.detail.header.successCopy": "商品番号がコピーされました。", "stuff.detail.header.failCopy": "存在しないものです。", "stuff.detail.header.objectNo": "商品番号のコピーに失敗しました。", - "stuff.detail.header.specificationConfirmDate": "仕様拡張日", + "stuff.detail.header.specificationConfirmDate": "仕様確認日", "stuff.detail.header.lastEditDatetime": "更新日時", "stuff.detail.header.createDatetime": "登録日", "stuff.detail.required": "必須入力項目", @@ -664,6 +666,7 @@ "stuff.detail.tooltip.saleStoreId": "販売代理店または販売代理店IDを1文字以上入力してください", "stuff.detail.tooltip.surfaceType": "塩害地域の定義は各メーカーの設置マニュアルをご確認ください", "stuff.detail.tempSave.message1": "一時保存されました。商品番号を取得するには、必須項目をすべて入力してください。", + "stuff.detail.tempSave.message2": "担当者は10桁以下で入力してください.", "stuff.detail.confirm.message1": "販売店情報を変更すると、設計依頼文書番号が削除されます。変更しますか?", "stuff.detail.delete.message1": "仕様が確定したものは削除できません。", "stuff.detail.planList.title": "プランリスト", @@ -725,7 +728,7 @@ "stuff.search.grid.all": "全体", "stuff.search.grid.selected": "選択", "stuff.search.grid.schSortTypeR": "最近の登録日", - "stuff.search.grid.schSortTypeU": "最近の更新日", + "stuff.search.grid.schSortTypeU": "最近修正日", "stuff.windSelectPopup.title": "風速選択", "stuff.windSelectPopup.table.selected": "選択", "stuff.windSelectPopup.table.windspeed": "風速", @@ -799,11 +802,14 @@ "main.storeName": "販売店名", "main.objectNo": "物件番号", "main.faq": "FAQ", + "main.content.objectList.noData1": "登録された商品情報はありません.", + "main.content.objectList.noData2": "下のボタンをクリックして商品情報を登録してください.", "main.content.objectList": "最近の更新物件一覧", "main.content.notice": "お知らせ", "main.content.download1": "操作マニュアル", "main.content.download2": "屋根の説明書", "main.content.noBusiness": "Hanwha Japanにお問い合わせください", + "main.content.alert.noFile": "資料が準備中です", "main.popup.login.popupTitle": "パスワード変更", "main.popup.login.newPassword1": "新しいパスワードを入力", "main.popup.login.newPassword2": "新規パスワード再入力", @@ -934,5 +940,10 @@ "simulator.table.sub9": "予測発電量 (kWh)", "simulator.notice.sub1": "Hanwha Japan 年間発電量", "simulator.notice.sub2": "シミュレーション案内事項", - "master.moduletypeitem.message.error": "지붕재 코드를 입력하세요." + "master.moduletypeitem.message.error": "지붕재 코드를 입력하세요.", + "can.not.move.module": "모듈을 이동할 수 없습니다.(JA)", + "can.not.copy.module": "모듈을 복사할 수 없습니다.(JA)", + "can.not.remove.module": "모듈을 삭제할 수 없습니다.(JA)", + "can.not.insert.module": "모듈을 삽입할 수 없습니다.(JA)", + "can.not.align.module": "모듈을 정렬할 수 없습니다.(JA)" } diff --git a/src/locales/ko.json b/src/locales/ko.json index 7fb64869..71e7943f 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -161,6 +161,7 @@ "plan.menu.estimate": "견적서", "plan.menu.estimate.roof.alloc": "지붕면 할당", "modal.roof.alloc.info": "※ 배치면 초기설정에서 저장한 [기본 지붕재]를 변경하거나, 지붕재를 추가하여 할당할 수 있습니다.", + "modal.roof.alloc.default.roof.material": "기본지붕재", "modal.roof.alloc.select.roof.material": "지붕재 선택", "modal.roof.alloc.select.parallel": "병렬식", "modal.roof.alloc.select.stairs": "계단식", @@ -301,7 +302,6 @@ "modal.actual.size.setting.plane.size.length": "복도치수 길이", "modal.actual.size.setting.actual.size.length": "실제치수 길이", "plan.message.confirm.save": "PLAN을 저장하시겠습니까?", - "plan.message.confirm.save.modified": "PLAN의 변경사항을 저장하시겠습니까?", "plan.message.confirm.copy": "PLAN을 복사하시겠습니까?", "plan.message.confirm.delete": "PLAN을 삭제하시겠습니까?", "plan.message.save": "저장되었습니다.", @@ -524,6 +524,8 @@ "color.darkblue": "남색", "site.name": "Q.CAST III", "site.sub_name": "태양광 발전 시스템 도면관리 사이트", + "site.header.link1": "선택하세요.", + "site.header.link2": "온라인보증시스템", "board.notice.title": "공지사항", "board.notice.sub.title": "공지사항 목록", "board.faq.title": "FAQ", @@ -640,7 +642,7 @@ "stuff.detail.header.successCopy": "물건번호가 복사되었습니다.", "stuff.detail.header.failCopy": "물건번호 복사에 실패했습니다.", "stuff.detail.header.objectNo": "물건번호", - "stuff.detail.header.specificationConfirmDate": "사양확장일", + "stuff.detail.header.specificationConfirmDate": "사양확정일", "stuff.detail.header.lastEditDatetime": "갱신일시", "stuff.detail.header.createDatetime": "등록일", "stuff.detail.required": "필수 입력항목", @@ -674,6 +676,7 @@ "stuff.detail.tooltip.saleStoreId": "판매대리점 또는 판매대리점ID를 1자 이상 입력하세요", "stuff.detail.tooltip.surfaceType": "염해지역 정의는 각 메이커의 설치 메뉴얼을 확인해주십시오", "stuff.detail.tempSave.message1": "임시저장 되었습니다. 물건번호를 획득하려면 필수 항목을 모두 입력해 주십시오.", + "stuff.detail.tempSave.message2": "담당자는 10자리 이하로 입력해 주십시오.", "stuff.detail.confirm.message1": "판매점 정보를 변경하면, 설계의뢰 문서번호가 삭제됩니다. 변경하시겠습니까?", "stuff.detail.delete.message1": "사양이 확정된 물건은 삭제할 수 없습니다.", "stuff.detail.planList.title": "플랜리스트", @@ -735,7 +738,7 @@ "stuff.search.grid.all": "전체", "stuff.search.grid.selected": "선택", "stuff.search.grid.schSortTypeR": "최근 등록일", - "stuff.search.grid.schSortTypeU": "최근 갱신일", + "stuff.search.grid.schSortTypeU": "최근 수정일", "stuff.windSelectPopup.title": "풍속선택", "stuff.windSelectPopup.table.selected": "선택", "stuff.windSelectPopup.table.windspeed": "풍속", @@ -809,11 +812,14 @@ "main.storeName": "판매점명", "main.objectNo": "물건번호", "main.faq": "FAQ", + "main.content.objectList.noData1": "등록된 물건정보가 없습니다.", + "main.content.objectList.noData2": "아래 버튼을 클릭하여 물건정보를 등록하십시오.", "main.content.objectList": "최근 갱신 물건목록", "main.content.notice": "공지사항", "main.content.download1": "조작메뉴얼", "main.content.download2": "지붕설명서", "main.content.noBusiness": "Hanwha Japan에 문의하세요", + "main.content.alert.noFile": "자료가 준비중입니다", "main.popup.login.popupTitle": "비밀번호변경", "main.popup.login.newPassword1": "새 비밀번호 입력", "main.popup.login.newPassword2": "새 비밀번호 재입력", @@ -944,5 +950,10 @@ "simulator.table.sub9": "예측발전량 (kWh)", "simulator.notice.sub1": "Hanwha Japan 연간 발전량", "simulator.notice.sub2": "시뮬레이션 안내사항", - "master.moduletypeitem.message.error": "지붕재 코드를 입력하세요." + "master.moduletypeitem.message.error": "지붕재 코드를 입력하세요.", + "can.not.move.module": "모듈을 이동할 수 없습니다.", + "can.not.copy.module": "모듈을 복사할 수 없습니다.", + "can.not.remove.module": "모듈을 삭제할 수 없습니다.", + "can.not.insert.module": "모듈을 삽입할 수 없습니다.", + "can.not.align.module": "모듈을 정렬할 수 없습니다." } diff --git a/src/models/apiModels.js b/src/models/apiModels.js new file mode 100644 index 00000000..3feb0428 --- /dev/null +++ b/src/models/apiModels.js @@ -0,0 +1,42 @@ +// 가대 목록 Request Models +export const trestleRequestModels = { + moduleTpCd: '', + roofMatlCd: '', + raftBaseCd: '', + trestleMkrCd: '', + constMthdCd: '', + roofBaseCd: '', +} + +// 시공법 목록 Request Models +export const constructionRequestModels = { + moduleTpCd: '', + roofMatlCd: '', + trestleMkrCd: '', + constMthdCd: '', + roofBaseCd: '', + illuminationTp: '', + instHt: '', + stdWindSpeed: '', + stdSnowLd: '', + inclCd: '', + raftBaseCd: '', + roofPitch: 0, +} + +// 가대 상세 Request Models +export const trestleDetailRequestModels = { + moduleTpCd: '', + roofMatlCd: '', + trestleMkrCd: '', + constMthdCd: '', + roofBaseCd: '', + illuminationTp: '', + instHt: '', + stdWindSpeed: '', + stdSnowLd: '', + inclCd: '', + constTp: '', + mixMatlNo: 0, + roofPitch: 0, +} diff --git a/src/store/canvasAtom.js b/src/store/canvasAtom.js index b0e21d41..89f7e14d 100644 --- a/src/store/canvasAtom.js +++ b/src/store/canvasAtom.js @@ -272,17 +272,6 @@ export const plansState = atom({ default: [], }) -// 변경된 canvas plan 목록 -export const modifiedPlansState = atom({ - key: 'modifiedPlansState', - default: [], -}) -// 변경감지 flag -export const modifiedPlanFlagState = atom({ - key: 'modifiedPlanFlagState', - default: false, -}) - export const tempGridModeState = atom({ key: 'tempGridModeState', default: false, diff --git a/src/store/settingAtom.js b/src/store/settingAtom.js index 7a07175b..305bcb57 100644 --- a/src/store/settingAtom.js +++ b/src/store/settingAtom.js @@ -203,8 +203,15 @@ export const basicSettingState = atom({ default: { roofSizeSet: 1, roofAngleSet: 'slope', - selectedRoofMaterial: {}, + selectedRoofMaterial: {}, // 선택된 지붕재 + roofs: [], // 지붕면 할당에서 추가된 지붕재 목록 }, + dangerouslyAllowMutability: true, +}) + +export const addedRoofsState = atom({ + key: 'addedRoofsState', + default: [], }) // db에 등록된 지붕재 목록 @@ -213,6 +220,23 @@ export const roofMaterialsAtom = atom({ default: [], }) +//현재 선택된 지붕재 +export const selectedRoofMaterialSelector = selector({ + key: 'selectedRoofMaterialSelector', + get: ({ get }) => { + return get(basicSettingState).selectedRoofMaterial + }, +}) + +// QSelectBox에서 사용할 지붕재 목록 +export const roofMaterialsSelector = selector({ + key: 'roofMaterialsSelector', + get: ({ get }) => { + const roofMaterials = get(roofMaterialsAtom) + return roofMaterials.map((material) => ({ ...material, id: material.roofMatlCd, name: material.roofMatlNm })) + }, +}) + /** * 현재 선택된 물건 번호 */ diff --git a/src/store/stuffAtom.js b/src/store/stuffAtom.js index 06593e2b..d30baab7 100644 --- a/src/store/stuffAtom.js +++ b/src/store/stuffAtom.js @@ -17,7 +17,7 @@ export const stuffSearchState = atom({ schOtherSelSaleStoreId: '', //1차 이외 판매대리점 선택 startRow: 1, endRow: 100, - schSortType: 'R', //정렬조건 (R:최근등록일 U:최근수정일) + schSortType: 'U', //정렬조건 (R:최근등록일 U:최근수정일) pageNo: 1, pageSize: 100, }, diff --git a/src/styles/_contents.scss b/src/styles/_contents.scss index e2ab95b7..e9d41117 100644 --- a/src/styles/_contents.scss +++ b/src/styles/_contents.scss @@ -17,1501 +17,1563 @@ // } // } // CanvasMenu -.canvas-menu-wrap{ - position: fixed; - top: 46px; - left: 0; - display: block; - width: 100%; - min-width: 1280px; - padding-bottom: 0; - background-color: #383838; - transition: padding .17s ease-in-out; - z-index: 999; - .canvas-menu-inner{ - position: relative; - display: flex; - align-items: center; - padding: 0 40px 0 20px; - background-color: #2C2C2C; - height: 46.8px; - z-index: 999; - .canvas-menu-list{ - display: flex; - align-items: center; - height: 100%; - .canvas-menu-item{ - display: flex; - align-items: center; - height: 100%; - button{ - display: flex; - align-items: center; - font-size: 12px; - height: 100%; - color: #fff; - font-weight: 600; - padding: 15px 20px; - opacity: 0.55; - transition: all .17s ease-in-out; - .menu-icon{ - display: block; - width: 14px; - height: 14px; - background-repeat: no-repeat; - background-position: center; - background-size: contain; - margin-right: 10px; - &.con00{background-image: url(/static/images/canvas/menu_icon00.svg);} - &.con01{background-image: url(/static/images/canvas/menu_icon01.svg);} - &.con02{background-image: url(/static/images/canvas/menu_icon02.svg);} - &.con03{background-image: url(/static/images/canvas/menu_icon03.svg);} - &.con04{background-image: url(/static/images/canvas/menu_icon04.svg);} - &.con05{background-image: url(/static/images/canvas/menu_icon05.svg);} - &.con06{background-image: url(/static/images/canvas/menu_icon06.svg);} - } - } - &.active{ - background-color: #383838; - button{ - opacity: 1; - } - } - } - } - .canvas-side-btn-wrap{ - display: flex; - align-items: center; - margin-left: auto; - .select-box{ - width: 124px; - margin: 0 5px; - height: 30px; - > div{ - width: 100%; - } - } - .btn-from{ - display: flex; - align-items: center; - gap: 5px; - button{ - display: block; - width: 30px; - height: 30px; - border-radius: 2px; - background-color: #3D3D3D; - background-position: center; - background-repeat: no-repeat; - background-size: 15px 15px; - transition: all .17s ease-in-out; - &.btn01{background-image: url(../../public/static/images/canvas/side_icon03.svg);} - &.btn02{background-image: url(../../public/static/images/canvas/side_icon02.svg);} - &.btn03{background-image: url(../../public/static/images/canvas/side_icon01.svg);} - &.btn04{background-image: url(../../public/static/images/canvas/side_icon04.svg);} - &.btn05{background-image: url(../../public/static/images/canvas/side_icon05.svg);} - &.btn06{background-image: url(../../public/static/images/canvas/side_icon06.svg);} - &.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; - } - &.active{ - background-color: #1083E3; - } - } - } - .ico-btn-from{ - display: flex; - align-items: center; - gap: 5px; - button{ - .ico{ - display: block; - width: 15px; - height: 15px; - background-repeat: no-repeat; - background-position: center; - background-size: contain; - &.ico01{background-image: url(../../public/static/images/canvas/ico-flx01.svg);} - &.ico02{background-image: url(../../public/static/images/canvas/ico-flx02.svg);} - &.ico03{background-image: url(../../public/static/images/canvas/ico-flx03.svg);} - &.ico04{background-image: url(../../public/static/images/canvas/ico-flx04.svg);} - &.ico05{background-image: url(../../public/static/images/canvas/ico-flx05.svg);} - } - .name{ - font-size: 12px; - color: #fff; - } - } - &.form06{ - .name{ - font-size: 13px; - } - } - } - .vertical-horizontal{ - display: flex; - min-width: 170px; - height: 28px; - margin-right: 5px; - border-radius: 2px; - background: #373737; - line-height: 28px; - overflow: hidden; - span{ - padding: 0 10px; - font-size: 13px; - color: #fff; - } - button{ - margin-left: auto; - height: 100%; - background-color: #4B4B4B; - font-size: 13px; - font-weight: 400; - color: #fff; - padding: 0 7.5px; - transition: all .17s ease-in-out; - } - &.on{ - button{ - background-color: #1083E3; - } - } - } - .size-control{ - display: flex; - align-items: center; - justify-content: center; - gap: 10px; - background-color: #3D3D3D; - border-radius: 2px; - width: 100px; - height: 30px; - margin: 0 5px; - span{ - font-size: 13px; - color: #fff; - cursor: pointer; - } - .control-btn{ - display: block; - width: 12px; - height: 12px; - background-repeat: no-repeat; - background-size: cover; - background-position: center; - &.minus{ - background-image: url(../../public/static/images/canvas/minus.svg); - } - &.plus{ - background-image: url(../../public/static/images/canvas/plus.svg); - } - } - } - } - } - .canvas-depth2-wrap{ - position: absolute; - top: -100%; - left: 0; - background-color: #383838; - width: 100%; - height: 50px; - transition: all .17s ease-in-out; - .canvas-depth2-inner{ - display: flex; - align-items: center; - padding: 0 40px; - height: 100%; - .canvas-depth2-list{ - display: flex; - align-items: center ; - height: 100%; - .canvas-depth2-item{ - display: flex; - align-items: center; - margin-right: 26px; - height: 100%; - button{ - position: relative; - opacity: 0.55; - color: #fff; - font-size: 12px; - font-weight: normal; - height: 100%; - padding-right: 12px; - } - &.active{ - button{ - opacity: 1; - font-weight: 600; - &:after{ - content: ''; - position: absolute; - top: 50%; - right: 0; - transform: translateY(-50%); - width: 5px; - height: 8px; - background: url(../../public/static/images/canvas/depth2-arr.svg) no-repeat center; - } - } - } - } - } - .canvas-depth2-btn-list{ - display: flex; - align-items: center; - margin-left: auto; - height: 100%; - .depth2-btn-box{ - display: flex; - align-items: center; - margin-right: 34px; - height: 100%; - transition: all .17s ease-in-out; - button{ - position: relative; - font-size: 12px; - font-weight: 400; - height: 100%; - color: #fff; - padding-right: 12px; - &:after{ - content: ''; - position: absolute; - top: 50%; - right: 0; - transform: translateY(-50%); - width: 5px; - height: 8px; - background: url(../../public/static/images/canvas/depth2-arr.svg) no-repeat center; - } - } - &:last-child{ - margin-right: 0; - } - &.mouse{ - opacity: 0.55; - } - } - } - } - &.active{ - top: 47px; - } - } - &.active{ - padding-bottom: 50px; - } -} - -// canvas-layout -.canvas-content{ - padding-top: 46.8px; - transition: all .17s ease-in-out; - .canvas-frame{ - height: calc(100vh - 129.3px); - } - &.active{ - padding-top: calc(46.8px + 50px); - .canvas-frame{ - height: calc(100vh - 179.4px); - } - } -} -.canvas-layout{ - padding-top: 37px; - .canvas-page-list{ - position: fixed; - top: 92.8px; - left: 0; - display: flex; - background-color: #1C1C1C; - border-top: 1px solid #000; - width: 100%; - min-width: 1280px; - transition: all .17s ease-in-out; - z-index: 99; - &.active{ - top: calc(92.8px + 50px); - } - .canvas-plane-wrap{ - display: flex; - align-items: center; - max-width: calc(100% - 45px); - .canvas-page-box{ - display: flex; - align-items: center; - background-color: #1c1c1c; - padding: 9.6px 20px; - border-right:1px solid #000; - min-width: 0; - transition: all .17s ease-in-out; - span{ - display: flex; - align-items: center; - width: 100%; - font-size: 12px; - font-family: 'Pretendard', sans-serif; - color: #AAA; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - } - .close{ - flex: none; - display: block; - width: 7px; - height: 8px; - margin-left: 15px; - background: url(../../public/static/images/canvas/plan_close_gray.svg)no-repeat center; - background-size: cover; - } - &.on{ - background-color: #fff; - span{ - font-weight: 600; - color: #101010; - } - .close{ - background: url(../../public/static/images/canvas/plan_close_black.svg)no-repeat center; - } - &:hover{ - background-color: #fff; - } - } - &:hover{ - background-color: #000; - } - } - } - .plane-add{ - display: flex; - align-items: center; - justify-content: center; - width: 45px; - padding: 13.5px 0; - background-color: #1C1C1C; - border-right: 1px solid #000; - transition: all .17s ease-in-out; - span{ - display: block; - width: 9px; - height: 9px; - background: url(../../public/static/images/canvas/plane_add.svg)no-repeat center; - background-size: cover; - } - &:hover{ - background-color: #000; - } - } - } -} - -.canvas-frame{ +.canvas-menu-wrap { + position: fixed; + top: 46px; + left: 0; + display: block; + width: 100%; + min-width: 1280px; + padding-bottom: 0; + background-color: #383838; + transition: padding 0.17s ease-in-out; + z-index: 999; + .canvas-menu-inner { position: relative; - // height: calc(100% - 36.5px); - background-color: #F4F4F7; - overflow: auto; - transition: all .17s ease-in-out; - // &::-webkit-scrollbar { - // width: 10px; - // height: 10px; - // background-color: #fff; - // } - // &::-webkit-scrollbar-thumb { - // background-color: #C1CCD7; - // border-radius: 30px; - // } - // &::-webkit-scrollbar-track { - // background-color: #fff; - // } - .canvas-container{ - margin: 0 auto; - background-color: #fff; - } - canvas{ - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - } -} - -// sub-page -.sub-header{ - position: fixed; - top: 46px; - left: 0; - width: 100%; - min-width: 1280px; - height: 46px; - border-bottom: 1px solid #000; - background: #2C2C2C; + display: flex; + align-items: center; + padding: 0 40px 0 20px; + background-color: #2c2c2c; + height: 46.8px; z-index: 999; - .sub-header-inner{ + .canvas-menu-list { + display: flex; + align-items: center; + height: 100%; + .canvas-menu-item { display: flex; align-items: center; height: 100%; - padding: 0 100px; - .sub-header-title-wrap{ - display: flex; - align-items: center; - .title-item{ - position: relative; - padding: 0 24px; - a{ - display: flex; - align-items: center; - .icon{ - width: 22px; - height: 22px; - margin-right: 8px; - background-repeat: no-repeat; - background-position: center; - background-size: cover; - &.drawing{background-image: url(../../public/static/images/main/drawing_icon.svg);} - } - } - &:after{ - content: ''; - position: absolute; - top: 50%; - right: 0; - transform: translateY(-50%); - width: 1px; - height: 16px; - background-color: #D9D9D9; - } - &:first-child{ - padding-left: 0; - } - &:last-child{ - padding-right: 0; - &:after{ - display: none; - } - } + button { + display: flex; + align-items: center; + font-size: 12px; + height: 100%; + color: #fff; + font-weight: 600; + padding: 15px 20px; + opacity: 0.55; + transition: all 0.17s ease-in-out; + .menu-icon { + display: block; + width: 14px; + height: 14px; + background-repeat: no-repeat; + background-position: center; + background-size: contain; + margin-right: 10px; + &.con00 { + background-image: url(/static/images/canvas/menu_icon00.svg); } + &.con01 { + background-image: url(/static/images/canvas/menu_icon01.svg); + } + &.con02 { + background-image: url(/static/images/canvas/menu_icon02.svg); + } + &.con03 { + background-image: url(/static/images/canvas/menu_icon03.svg); + } + &.con04 { + background-image: url(/static/images/canvas/menu_icon04.svg); + } + &.con05 { + background-image: url(/static/images/canvas/menu_icon05.svg); + } + &.con06 { + background-image: url(/static/images/canvas/menu_icon06.svg); + } + } } - .sub-header-title{ - font-size: 16px; + &.active { + background-color: #383838; + button { + opacity: 1; + } + } + } + } + .canvas-side-btn-wrap { + display: flex; + align-items: center; + margin-left: auto; + .select-box { + width: 124px; + margin: 0 5px; + height: 30px; + > div { + width: 100%; + } + } + .btn-from { + display: flex; + align-items: center; + gap: 5px; + button { + display: block; + width: 30px; + height: 30px; + border-radius: 2px; + background-color: #3d3d3d; + background-position: center; + background-repeat: no-repeat; + background-size: 15px 15px; + transition: all 0.17s ease-in-out; + &.btn01 { + background-image: url(../../public/static/images/canvas/side_icon03.svg); + } + &.btn02 { + background-image: url(../../public/static/images/canvas/side_icon02.svg); + } + &.btn03 { + background-image: url(../../public/static/images/canvas/side_icon01.svg); + } + &.btn04 { + background-image: url(../../public/static/images/canvas/side_icon04.svg); + } + &.btn05 { + background-image: url(../../public/static/images/canvas/side_icon05.svg); + } + &.btn06 { + background-image: url(../../public/static/images/canvas/side_icon06.svg); + } + &.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; + } + &.active { + background-color: #1083e3; + } + } + } + .ico-btn-from { + display: flex; + align-items: center; + gap: 5px; + button { + .ico { + display: block; + width: 15px; + height: 15px; + background-repeat: no-repeat; + background-position: center; + background-size: contain; + &.ico01 { + background-image: url(../../public/static/images/canvas/ico-flx01.svg); + } + &.ico02 { + background-image: url(../../public/static/images/canvas/ico-flx02.svg); + } + &.ico03 { + background-image: url(../../public/static/images/canvas/ico-flx03.svg); + } + &.ico04 { + background-image: url(../../public/static/images/canvas/ico-flx04.svg); + } + &.ico05 { + background-image: url(../../public/static/images/canvas/ico-flx05.svg); + } + } + .name { + font-size: 12px; color: #fff; - font-weight: 600; + } } - .sub-header-location{ - margin-left: auto; - display: flex; - align-items: center; - .location-item{ - position: relative; - display: flex; - align-items: center; - padding: 0 10px; - span{ - display: flex; - font-size: 12px; - color: #AAA; - font-weight: normal; - cursor: default; - } - &:after{ - content: ''; - position: absolute; - top: 50%; - right: 0; - transform: translateY(-50%); - width: 4px; - height: 6px; - background: url(../../public/static/images/main/loaction_arr.svg)no-repeat center; - } - &:first-child{ - padding-left: 0; - } - &:last-child{ - padding-right: 0; - span{ - color: #fff; - } - &:after{ - display: none; - } - } - } + &.form06 { + .name { + font-size: 13px; + } } - } -} - -// sub content -.sub-content{ - padding-top: 46px; - .sub-content-inner{ - max-width: 1760px; - margin: 0 auto; - padding: 20px 20px 0; - .sub-content-box{ - margin-bottom: 20px; - &:last-child{ - margin-bottom: 0; - } - } - } - &.estimate{ + } + .vertical-horizontal { display: flex; - flex-direction: column; - padding-top: 0; - .sub-content-inner{ - flex: 1; - width: 100%; + min-width: 170px; + height: 28px; + margin-right: 5px; + border-radius: 2px; + background: #373737; + line-height: 28px; + overflow: hidden; + span { + padding: 0 10px; + font-size: 13px; + color: #fff; } - } -} -.sub-table-box{ - padding: 20px; - border-radius: 6px; - border: 1px solid #E9EAED; - background: #FFF; - box-shadow: 0px 3px 30px 0px rgba(0, 0, 0, 0.02); - .table-box-title-wrap{ - display: flex; - align-items: center; - margin-bottom: 15px; - .title-wrap{ - display: flex; - align-items: center; - h3{ - display: block; - font-size: 15px; - color: #101010; - font-weight: 600; - margin-right: 14px; - &.product{ - margin-right: 10px; - } - } - .product_tit{ - position: relative; - font-size: 15px; - font-weight: 600; - color: #1083E3; - padding-left: 10px; - &::before{ - content: ''; - position: absolute; - top: 50%; - left: 0; - transform: translateY(-50%); - width: 1px; - height: 11px; - background-color: #D9D9D9; - } - } - .option{ - padding-left: 5px; - font-size: 13px; - color: #101010; - font-weight: 400; - } - .info-wrap{ - display: flex; - align-items: center; - li{ - position: relative; - padding: 0 6px; - font-size: 12px; - color: #101010; - font-weight: normal; - span{ - font-weight: 600; - &.red{ - color: #E23D70; - } - } - &:after{ - content: ''; - position: absolute; - top: 50%; - right: 0; - transform: translateY(-50%); - width: 1px; - height: 11px; - background-color: #D9D9D9; - } - &:first-child{padding-left: 0;} - &:last-child{padding-right: 0;&::after{display: none;}} - } - } + button { + margin-left: auto; + height: 100%; + background-color: #4b4b4b; + font-size: 13px; + font-weight: 400; + color: #fff; + padding: 0 7.5px; + transition: all 0.17s ease-in-out; } - } - .left-unit-box{ - margin-left: auto; - display: flex; - align-items: center; - } - .promise-gudie{ - display: block; - font-size: 13px; - font-weight: 700; - color: #101010; - margin-bottom: 20px; - } - .important{ - color: #f00; - } - .sub-center-footer{ + &.on { + button { + background-color: #1083e3; + } + } + } + .size-control { display: flex; align-items: center; justify-content: center; - margin-top: 20px; - } - .sub-right-footer{ - display: flex; - align-items: center; - justify-content: flex-end; - margin-top: 20px; - } -} -.pagination-wrap{ - margin-top: 24px; -} - -.infomation-wrap{ - margin-bottom: 30px; -} - -.infomation-box-wrap{ - display: flex; - gap: 10px; - .sub-table-box{ - flex: 1 ; - } - .info-title{ - font-size: 14px; - font-weight: 500; - color: #344356; - margin-bottom: 10px; - } - .info-inner{ - position: relative; - font-size: 13px; - color: #344356; - .copy-ico{ - position: absolute; - bottom: 0; - right: 0; - width: 16px; - height: 16px; - background: url(../../public/static/images/sub/copy_ico.svg)no-repeat center; - background-size: cover; + gap: 10px; + background-color: #3d3d3d; + border-radius: 2px; + width: 100px; + height: 30px; + margin: 0 5px; + span { + font-size: 13px; + color: #fff; + cursor: pointer; } - } -} - -// 견적서 -.estimate-list-wrap{ - display: flex; - align-items: center; - margin-bottom: 10px; - &.one{ - .estimate-box{ - &:last-child{ - flex: 1; - min-width: unset; - } + .control-btn { + display: block; + width: 12px; + height: 12px; + background-repeat: no-repeat; + background-size: cover; + background-position: center; + &.minus { + background-image: url(../../public/static/images/canvas/minus.svg); + } + &.plus { + background-image: url(../../public/static/images/canvas/plus.svg); + } } + } } - .estimate-box{ - flex: 1 ; - display: flex; - align-items: center; - &:last-child{ - flex: none; - min-width: 220px; - } - .estimate-tit{ - width: 105px; - height: 30px; - line-height: 30px; - background-color: #F4F4F7; - border-radius: 100px; - text-align: center; - font-size: 13px; - font-weight: 500; - color: #344356; - } - .estimate-name{ - font-size: 13px; - color: #344356; - margin-left: 14px; - font-weight: 400; - &.blue{ - font-size: 16px; - font-weight: 700; - color: #1083E3; - } - &.red{ - font-size: 16px; - font-weight: 700; - color: #D72A2A; - } - } - } - &:last-child{ - margin-bottom: 0; - } -} - -// file drag box -.drag-file-box{ - padding: 10px; - .btn-area{ - padding-bottom: 15px; - border-bottom: 1px solid #ECF0F4; - .file-upload{ - display: inline-block; - height: 30px; - background-color: #94A0AD; - padding: 0 10px; - border-radius: 2px; - font-size: 13px; - line-height: 30px; - color: #fff; - font-weight: 500; - cursor: pointer; - transition: background .15s ease-in-out; - &:hover{ - background-color: #607F9A; - } - } - } - .drag-file-area{ - position: relative; - margin-top: 15px; - p{ - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - font-size: 13px; - color: #ccc; - font-weight: 400; - cursor: default; - } - } - .file-list{ - min-height: 52px; - .file-item{ - margin-bottom: 15px; - span{ - position: relative; - font-size: 13px; - color: #45576F; - font-weight: 400; - white-space: nowrap; - padding-right: 55px; - cursor: pointer; - button{ - position: absolute; - top: 50%; - right: 0; - transform: translateY(-50%); - width: 15px; - height: 15px; - background: url(../../public/static/images/sub/file_delete.svg)no-repeat center; - background-size: cover; - } - } - &:last-child{ - margin-bottom: 0; - } - .file-item-wrap{ - display: flex; - align-items: center; - gap: 30px; - .return-wrap{ - display: flex; - align-items: center; - } - .return{ - padding: 0; - font-size: 13px; - color: #B0BCCD; - text-decoration: line-through; - } - .return-btn{ - flex: none; - position: relative; - top: 0; - left: 0; - transform: none; - display: flex; - align-items: center; - height: 24px; - padding: 0 9px; - margin-left: 10px; - background: none; - border: 1px solid #B0BCCD; - border-radius: 2px; - font-size: 12px; - color: #B0BCCD; - font-weight: 500; - .return-ico{ - display: block; - width: 14px; - height: 14px; - background: url(../../public/static/images/canvas/return-btn.svg)no-repeat center; - background-size: contain; - margin-right: 5px; - } - } - } - } - } -} - -.estimate-arr-btn{ - display: block; - width: 20px; - height: 20px; - background-color: #94A0AD; - border: 1px solid #94A0AD; - background-position: center; - background-repeat: no-repeat; - background-image: url(../../public/static/images/canvas/estiment_arr.svg); - background-size: 11px 7px; - border-radius: 2px; - &.up{ - rotate: 180deg; - } - &.on{ - background-color: #fff; - border-color: #C2D0DD; - background-image: url(../../public/static/images/canvas/estiment_arr_color.svg) - } -} -.estimate-check-wrap{ - .estimate-check-inner{ - display: block; - } - &.hide{ - border-bottom: 1px solid #ECF0F4; - margin-bottom: 15px; - .estimate-check-inner{ - display: none; - } - } -} - -.special-note-check-wrap{ - display: grid; - grid-template-columns: repeat(5, 1fr); - border-radius: 3px; - margin-bottom: 30px; - .special-note-check-item{ - padding: 14px 10px; - border: 1px solid #ECF0F4; - margin-top: -1px; - margin-right: -1px; - &.act{ - background-color: #F7F9FA; - } - .special-note-check-box{ - display: flex; - align-items: center; - .check-name{ - font-size: 13px; - color: #45576F; - cursor: pointer; - line-height: 1.3; - } - } - } -} - -.calculation-estimate{ - border: 1px solid #ECF0F4; - border-radius: 3px; - padding: 24px; - height: 350px; - overflow-y: auto; - margin-bottom: 30px; - dl{ - margin-bottom: 35px; - &:last-child{ - margin-bottom: 0; - } - dt{ - font-size: 13px; - font-weight: 600; - color: #1083E3; - margin-bottom: 15px; - } - dd{ - font-size: 12px; - font-weight: 400; - color: #45576F; - margin-bottom: 8px; - &:last-child{ - margin-bottom: 0; - } - } - } - &::-webkit-scrollbar { - width: 4px; - background-color: transparent; - } - &::-webkit-scrollbar-thumb { - background-color: #d9dee2; - } - &::-webkit-scrollbar-track { - background-color: transparent; - } -} -.esimate-wrap{ - margin-bottom: 20px; -} - -.estimate-product-option{ - display: flex; - align-items: center; - margin-bottom: 15px; - .product-price-wrap{ - display: flex; - align-items: center; - .product-price-tit{ - font-size: 13px; - font-weight: 400; - color: #45576F; - margin-right: 10px; - } - .select-wrap{ - width: 110px; - } - } - .product-edit-wrap{ - display: flex; - align-items: center; - margin-left: auto; - .product-edit-explane{ - display: flex; - align-items: center; - margin-right: 15px; - .explane-item{ - position: relative; - display: flex; - align-items: center; - padding: 0 10px; - font-size: 12px; - font-weight: 400; - span{ - width: 20px; - height: 20px; - margin-right: 5px; - background-size: cover; - background-repeat: no-repeat; - background-position: center; - } - &:before{ - content: ''; - position: absolute; - top: 50%; - left: 0; - transform: translateY(-50%); - width: 1px; - height: 12px; - background-color: #D9D9D9; - } - &:first-child{ - padding-left: 0; - &::before{ - display: none; - } - } - &:last-child{ - padding-right: 0; - } - &.item01{ - color: #3BBB48; - span{ - background-image: url(../../public/static/images/sub/open_ico.svg); - } - } - &.item02{ - color: #909000; - span{ - background-image: url(../../public/static/images/sub/change_ico.svg); - } - } - &.item03{ - color: #0191C9; - span{ - background-image: url(../../public/static/images/sub/attachment_ico.svg); - } - } - &.item04{ - color: #F16A6A; - span{ - background-image: url(../../public/static/images/sub/click_check_ico.svg); - } - } - } - } - .product-edit-btn{ - display: flex; - align-items: center; - button{ - display: flex; - align-items: center; - span{ - width: 13px; - height: 13px; - margin-right: 5px; - background-size: cover; - &.plus{ - background: url(../../public/static/images/sub/plus_btn.svg)no-repeat center; - } - &.minus{ - background: url(../../public/static/images/sub/minus_btn.svg)no-repeat center; - } - } - } - } - } -} - -// 발전시물레이션 -.chart-wrap{ - display: flex; - gap: 20px; + } + .canvas-depth2-wrap { + position: absolute; + top: -100%; + left: 0; + background-color: #383838; width: 100%; - .sub-table-box{ - height: 100%; - } - .chart-inner{ - flex: 1; - .chart-box{ - margin-bottom: 30px; - } - } - .chart-table-wrap{ + height: 50px; + transition: all 0.17s ease-in-out; + .canvas-depth2-inner { + display: flex; + align-items: center; + padding: 0 40px; + height: 100%; + .canvas-depth2-list { display: flex; - flex-direction: column; - flex: none; - width: 650px; - .sub-table-box{ - flex: 1; - &:first-child{ - margin-bottom: 20px; - } - } - } -} - -.chart-month-table{ - table{ - table-layout: fixed; - border-collapse:collapse; - border: 1px solid #ECF0F4; - border-radius: 4px; - thead{ - th{ - padding: 4.5px 0; - border-bottom: 1px solid #ECF0F4; - text-align: center; - font-size: 13px; - color: #45576F; - font-weight: 500; - background-color: #F8F9FA; - } - } - tbody{ - td{ - font-size: 13px; - color: #45576F; - text-align: center; - padding: 4.5px 0; - } - } - } -} - -.simulation-guide-wrap{ - display: flex; - padding: 20px; - .simulation-tit-wrap{ - flex: none; - padding-right: 40px; - border-right: 1px solid #EEEEEE; - span{ - display: block; + align-items: center; + height: 100%; + .canvas-depth2-item { + display: flex; + align-items: center; + margin-right: 26px; + height: 100%; + button { position: relative; - padding-left: 60px; - font-size: 15px; - color: #14324F; - font-weight: 600; - &::before{ + opacity: 0.55; + color: #fff; + font-size: 12px; + font-weight: normal; + height: 100%; + padding-right: 12px; + } + &.active { + button { + opacity: 1; + font-weight: 600; + &:after { content: ''; position: absolute; top: 50%; - left: 0; + right: 0; transform: translateY(-50%); - width: 40px; - height: 40px; - background: url(../../public/static/images/sub/simulation_guide.svg)no-repeat center; - background-size: cover; + width: 5px; + height: 8px; + background: url(../../public/static/images/canvas/depth2-arr.svg) no-repeat center; + } } + } } - } - .simulation-guide-box{ - flex: 1; - padding-left: 40px; - dl{ - margin-bottom: 25px; - dt{ - font-size: 13px; - color: #101010; - font-weight: 600; - margin-bottom: 5px; - } - dd{ - font-size: 12px; - color: #45576F; - font-weight: 400; - line-height: 24px; - } - &:last-child{ - margin-bottom: 0; - } - } - ul, ol{ - list-style: unset; - } - } -} - -.module-total{ - display: flex; - align-items: center; - background-color: #F8F9FA; - padding: 9px 0; - margin-right: 4px; - border: 1px solid #ECF0F4; - border-top: none; - .total-title{ - flex: 1; - text-align: center; - font-size: 13px; - color: #344356; - font-weight: 500; - } - .total-num{ - flex: none; - width: 121px; - text-align: center; - font-size: 15px; - color: #344356; - font-weight: 500; - } -} - -// 물건상세 -.information-help-wrap{ - display: flex; - padding: 24px; - background-color: #F4F4F4; - border-radius: 4px; - margin-bottom: 15px; - .information-help-tit-wrap{ - position: relative; + } + .canvas-depth2-btn-list { display: flex; align-items: center; - padding-right: 40px; - border-right: 1px solid #E0E0E3; - .help-tit-icon{ - width: 40px; - height: 40px; - border-radius: 50%; - margin-right: 10px; - background: #fff url(../../public/static/images/sub/information_help.svg)no-repeat center; - background-size: 20px 20px; - } - .help-tit{ - font-size: 13px; - font-weight: 600; - color: #45576F; - } - } - .information-help-guide{ - padding-left: 40px; - span{ - display: block; + margin-left: auto; + height: 100%; + .depth2-btn-box { + display: flex; + align-items: center; + margin-right: 34px; + height: 100%; + transition: all 0.17s ease-in-out; + button { + position: relative; font-size: 12px; font-weight: 400; - color: #45576F; - margin-bottom: 7px; - &:last-child{ - margin-bottom: 0; + height: 100%; + color: #fff; + padding-right: 12px; + &:after { + content: ''; + position: absolute; + top: 50%; + right: 0; + transform: translateY(-50%); + width: 5px; + height: 8px; + background: url(../../public/static/images/canvas/depth2-arr.svg) no-repeat center; } + } + &:last-child { + margin-right: 0; + } + &.mouse { + opacity: 0.55; + } } + } } + &.active { + top: 47px; + } + } + &.active { + padding-bottom: 50px; + } } -.community-search-warp{ +// canvas-layout +.canvas-content { + padding-top: 46.8px; + transition: all 0.17s ease-in-out; + .canvas-frame { + height: calc(100vh - 129.3px); + } + &.active { + padding-top: calc(46.8px + 50px); + .canvas-frame { + height: calc(100vh - 179.4px); + } + } +} +.canvas-layout { + padding-top: 37px; + .canvas-page-list { + position: fixed; + top: 92.8px; + left: 0; + display: flex; + background-color: #1c1c1c; + border-top: 1px solid #000; + width: 100%; + min-width: 1280px; + transition: all 0.17s ease-in-out; + z-index: 99; + &.active { + top: calc(92.8px + 50px); + } + .canvas-plane-wrap { + display: flex; + align-items: center; + max-width: calc(100% - 45px); + .canvas-page-box { + display: flex; + align-items: center; + background-color: #1c1c1c; + padding: 9.6px 20px; + border-right: 1px solid #000; + min-width: 0; + transition: all 0.17s ease-in-out; + span { + display: flex; + align-items: center; + width: 100%; + font-size: 12px; + font-family: 'Pretendard', sans-serif; + color: #aaa; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + } + .close { + flex: none; + display: block; + width: 7px; + height: 8px; + margin-left: 15px; + background: url(../../public/static/images/canvas/plan_close_gray.svg) no-repeat center; + background-size: cover; + } + &.on { + background-color: #fff; + span { + font-weight: 600; + color: #101010; + } + .close { + background: url(../../public/static/images/canvas/plan_close_black.svg) no-repeat center; + } + &:hover { + background-color: #fff; + } + } + &:hover { + background-color: #000; + } + } + } + .plane-add { + display: flex; + align-items: center; + justify-content: center; + width: 45px; + padding: 13.5px 0; + background-color: #1c1c1c; + border-right: 1px solid #000; + transition: all 0.17s ease-in-out; + span { + display: block; + width: 9px; + height: 9px; + background: url(../../public/static/images/canvas/plane_add.svg) no-repeat center; + background-size: cover; + } + &:hover { + background-color: #000; + } + } + } +} + +.canvas-frame { + position: relative; + // height: calc(100% - 36.5px); + background-color: #f4f4f7; + overflow: auto; + transition: all 0.17s ease-in-out; + // &::-webkit-scrollbar { + // width: 10px; + // height: 10px; + // background-color: #fff; + // } + // &::-webkit-scrollbar-thumb { + // background-color: #C1CCD7; + // border-radius: 30px; + // } + // &::-webkit-scrollbar-track { + // background-color: #fff; + // } + .canvas-container { + margin: 0 auto; + background-color: #fff; + } + canvas { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } +} + +// sub-page +.sub-header { + position: fixed; + top: 46px; + left: 0; + width: 100%; + min-width: 1280px; + height: 46px; + border-bottom: 1px solid #000; + background: #2c2c2c; + z-index: 999; + .sub-header-inner { display: flex; - flex-direction: column; align-items: center; - padding: 10px 0 30px 0; - border-bottom: 1px solid #E5E5E5; - margin-bottom: 24px; - .community-search-box{ + height: 100%; + padding: 0 100px; + .sub-header-title-wrap { + display: flex; + align-items: center; + .title-item { + position: relative; + padding: 0 24px; + a { + display: flex; + align-items: center; + .icon { + width: 22px; + height: 22px; + margin-right: 8px; + background-repeat: no-repeat; + background-position: center; + background-size: cover; + &.drawing { + background-image: url(../../public/static/images/main/drawing_icon.svg); + } + } + } + &:after { + content: ''; + position: absolute; + top: 50%; + right: 0; + transform: translateY(-50%); + width: 1px; + height: 16px; + background-color: #d9d9d9; + } + &:first-child { + padding-left: 0; + } + &:last-child { + padding-right: 0; + &:after { + display: none; + } + } + } + } + .sub-header-title { + font-size: 16px; + color: #fff; + font-weight: 600; + } + .sub-header-location { + margin-left: auto; + display: flex; + align-items: center; + .location-item { position: relative; display: flex; align-items: center; - width: 580px; - height: 45px; - padding: 0 45px 0 20px; - margin-bottom: 20px; - border-radius: 2px; - border: 1px solid #101010; - .community-input{ - width: 100%; - height: 100%; - font-size: 13px; - font-weight: 400; - color: #101010; - &::placeholder{ - color: #C8C8C8; - } + padding: 0 10px; + span { + display: flex; + font-size: 12px; + color: #aaa; + font-weight: normal; + cursor: default; } - .community-search-ico{ - position: absolute; - top: 50%; - right: 20px; - transform: translateY(-50%); - flex: none; - width: 21px; - height: 100%; - background: url(../../public/static/images/sub/community_search.svg)no-repeat center; - background-size: 21px 21px; - z-index: 3; - } - } - .community-search-keyword{ - font-size: 13px; - font-weight: 400; - color: #45576F; - span{ - font-weight: 600; - color: #F16A6A; + &:after { + content: ''; + position: absolute; + top: 50%; + right: 0; + transform: translateY(-50%); + width: 4px; + height: 6px; + background: url(../../public/static/images/main/loaction_arr.svg) no-repeat center; } + &:first-child { + padding-left: 0; + } + &:last-child { + padding-right: 0; + span { + color: #fff; + } + &:after { + display: none; + } + } + } } + } } -// 자료 다운로드 -.file-down-list{ - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 14px; - .file-down-item{ +// sub content +.sub-content { + padding-top: 46px; + .sub-content-inner { + max-width: 1760px; + margin: 0 auto; + padding: 20px 20px 0; + .sub-content-box { + margin-bottom: 20px; + &:last-child { + margin-bottom: 0; + } + } + } + &.estimate { + display: flex; + flex-direction: column; + padding-top: 0; + .sub-content-inner { + flex: 1; + width: 100%; + } + } +} +.sub-table-box { + padding: 20px; + border-radius: 6px; + border: 1px solid #e9eaed; + background: #fff; + box-shadow: 0px 3px 30px 0px rgba(0, 0, 0, 0.02); + .table-box-title-wrap { + display: flex; + align-items: center; + margin-bottom: 15px; + .title-wrap { + display: flex; + align-items: center; + h3 { + display: block; + font-size: 15px; + color: #101010; + font-weight: 600; + margin-right: 14px; + &.product { + margin-right: 10px; + } + } + .product_tit { + position: relative; + font-size: 15px; + font-weight: 600; + color: #1083e3; + padding-left: 10px; + &::before { + content: ''; + position: absolute; + top: 50%; + left: 0; + transform: translateY(-50%); + width: 1px; + height: 11px; + background-color: #d9d9d9; + } + } + .option { + padding-left: 5px; + font-size: 13px; + color: #101010; + font-weight: 400; + } + .info-wrap { display: flex; align-items: center; - padding: 24px; - border-radius: 4px; - border: 1px solid #E5E5E5; - background: #FFF; - transition: all .15s ease-in-out; - .file-item-info{ - .item-num{ - display: inline-block; - padding: 6px 17.5px; - border-radius: 60px; - background-color: #F4F4F7; - font-size: 13px; - font-weight: 600; - color: #101010; - margin-bottom: 15px; + li { + position: relative; + padding: 0 6px; + font-size: 12px; + color: #101010; + font-weight: normal; + span { + font-weight: 600; + &.red { + color: #e23d70; } - .item-name{ - font-size: 16px; - color: #101010; - font-weight: 500; - margin-bottom: 13px; - } - .item-date{ - font-size: 13px; - font-weight: 400; - color: #344356; + } + &:after { + content: ''; + position: absolute; + top: 50%; + right: 0; + transform: translateY(-50%); + width: 1px; + height: 11px; + background-color: #d9d9d9; + } + &:first-child { + padding-left: 0; + } + &:last-child { + padding-right: 0; + &::after { + display: none; } + } } - .file-down-box{ - display: flex; - align-items: center; - flex: none; - margin-left: auto; - height: 100%; - .file-down-btn{ - width: 36px; - height: 36px; - background: url(../../public/static/images/sub/file_down_btn.svg)no-repeat center; - background-size: cover; - } - } - &:hover{ - background-color: #F4F4F7; - } + } } -} - -.file-down-nodata{ + } + .left-unit-box { + margin-left: auto; + display: flex; + align-items: center; + } + .promise-title-wrap { + display: flex; + align-items: center; + margin-bottom: 15px; + .promise-gudie { + margin-bottom: 0; + } + } + .promise-gudie { + display: block; + font-size: 13px; + font-weight: 700; + color: #101010; + margin-bottom: 20px; + } + .important { + color: #f00; + } + .sub-center-footer { display: flex; align-items: center; justify-content: center; - width: 100%; - height: 148px; - padding: 24px; - border-radius: 4px; - border: 1px solid #E5E5E5; - font-size: 16px; + margin-top: 20px; + } + .sub-right-footer { + display: flex; + align-items: center; + justify-content: flex-end; + margin-top: 20px; + } +} +.pagination-wrap { + margin-top: 24px; +} + +.infomation-wrap { + margin-bottom: 30px; +} + +.infomation-box-wrap { + display: flex; + gap: 10px; + .sub-table-box { + flex: 1; + } + .info-title { + font-size: 14px; font-weight: 500; color: #344356; + margin-bottom: 10px; + } + .info-inner { + position: relative; + font-size: 13px; + color: #344356; + .copy-ico { + position: absolute; + bottom: 0; + right: 0; + width: 16px; + height: 16px; + background: url(../../public/static/images/sub/copy_ico.svg) no-repeat center; + background-size: cover; + } + } +} + +// 견적서 +.estimate-list-wrap { + display: flex; + align-items: center; + margin-bottom: 10px; + &.one { + .estimate-box { + &:last-child { + flex: 1; + min-width: unset; + } + } + } + .estimate-box { + flex: 1; + display: flex; + align-items: center; + &:last-child { + flex: none; + min-width: 220px; + } + .estimate-tit { + width: 105px; + height: 30px; + line-height: 30px; + background-color: #f4f4f7; + border-radius: 100px; + text-align: center; + font-size: 13px; + font-weight: 500; + color: #344356; + } + .estimate-name { + font-size: 13px; + color: #344356; + margin-left: 14px; + font-weight: 400; + &.blue { + font-size: 16px; + font-weight: 700; + color: #1083e3; + } + &.red { + font-size: 16px; + font-weight: 700; + color: #d72a2a; + } + } + } + &:last-child { + margin-bottom: 0; + } +} + +// file drag box +.drag-file-box { + padding: 10px; + .btn-area { + padding-bottom: 15px; + border-bottom: 1px solid #ecf0f4; + .file-upload { + display: inline-block; + height: 30px; + background-color: #94a0ad; + padding: 0 10px; + border-radius: 2px; + font-size: 13px; + line-height: 30px; + color: #fff; + font-weight: 500; + cursor: pointer; + transition: background 0.15s ease-in-out; + &:hover { + background-color: #607f9a; + } + } + } + .drag-file-area { + position: relative; + margin-top: 15px; + p { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 13px; + color: #ccc; + font-weight: 400; + cursor: default; + } + } + .file-list { + min-height: 52px; + .file-item { + margin-bottom: 15px; + span { + position: relative; + font-size: 13px; + color: #45576f; + font-weight: 400; + white-space: nowrap; + padding-right: 55px; + cursor: pointer; + button { + position: absolute; + top: 50%; + right: 0; + transform: translateY(-50%); + width: 15px; + height: 15px; + background: url(../../public/static/images/sub/file_delete.svg) no-repeat center; + background-size: cover; + } + } + &:last-child { + margin-bottom: 0; + } + .file-item-wrap { + display: flex; + align-items: center; + gap: 30px; + .return-wrap { + display: flex; + align-items: center; + } + .return { + padding: 0; + font-size: 13px; + color: #b0bccd; + text-decoration: line-through; + } + .return-btn { + flex: none; + position: relative; + top: 0; + left: 0; + transform: none; + display: flex; + align-items: center; + height: 24px; + padding: 0 9px; + margin-left: 10px; + background: none; + border: 1px solid #b0bccd; + border-radius: 2px; + font-size: 12px; + color: #b0bccd; + font-weight: 500; + .return-ico { + display: block; + width: 14px; + height: 14px; + background: url(../../public/static/images/canvas/return-btn.svg) no-repeat center; + background-size: contain; + margin-right: 5px; + } + } + } + } + } +} + +.estimate-arr-btn { + display: block; + width: 20px; + height: 20px; + background-color: #94a0ad; + border: 1px solid #94a0ad; + background-position: center; + background-repeat: no-repeat; + background-image: url(../../public/static/images/canvas/estiment_arr.svg); + background-size: 11px 7px; + border-radius: 2px; + &.up { + rotate: 180deg; + } + &.on { + background-color: #fff; + border-color: #c2d0dd; + background-image: url(../../public/static/images/canvas/estiment_arr_color.svg); + } +} +.estimate-check-wrap { + .estimate-check-inner { + display: block; + } + &.hide { + border-bottom: 1px solid #ecf0f4; + margin-bottom: 15px; + .estimate-check-inner { + display: none; + } + } +} + +.special-note-check-wrap { + display: grid; + grid-template-columns: repeat(5, 1fr); + border-radius: 3px; + margin-bottom: 30px; + .special-note-check-item { + padding: 14px 10px; + border: 1px solid #ecf0f4; + margin-top: -1px; + margin-right: -1px; + &.act { + background-color: #f7f9fa; + } + .special-note-check-box { + display: flex; + align-items: center; + .check-name { + font-size: 13px; + color: #45576f; + cursor: pointer; + line-height: 1.3; + } + } + } +} + +.calculation-estimate { + border: 1px solid #ecf0f4; + border-radius: 3px; + padding: 24px; + height: 350px; + overflow-y: auto; + margin-bottom: 30px; + dl { + margin-bottom: 35px; + &:last-child { + margin-bottom: 0; + } + dt { + font-size: 13px; + font-weight: 600; + color: #1083e3; + margin-bottom: 15px; + } + dd { + font-size: 12px; + font-weight: 400; + color: #45576f; + margin-bottom: 8px; + &:last-child { + margin-bottom: 0; + } + } + } + &::-webkit-scrollbar { + width: 4px; + background-color: transparent; + } + &::-webkit-scrollbar-thumb { + background-color: #d9dee2; + } + &::-webkit-scrollbar-track { + background-color: transparent; + } +} +.esimate-wrap { + margin-bottom: 20px; +} + +.estimate-product-option { + display: flex; + align-items: center; + margin-bottom: 15px; + .product-price-wrap { + display: flex; + align-items: center; + .product-price-tit { + font-size: 13px; + font-weight: 400; + color: #45576f; + margin-right: 10px; + } + .select-wrap { + width: 110px; + } + } + .product-edit-wrap { + display: flex; + align-items: center; + margin-left: auto; + .product-edit-explane { + display: flex; + align-items: center; + margin-right: 15px; + .explane-item { + position: relative; + display: flex; + align-items: center; + padding: 0 10px; + font-size: 12px; + font-weight: 400; + span { + width: 20px; + height: 20px; + margin-right: 5px; + background-size: cover; + background-repeat: no-repeat; + background-position: center; + } + &:before { + content: ''; + position: absolute; + top: 50%; + left: 0; + transform: translateY(-50%); + width: 1px; + height: 12px; + background-color: #d9d9d9; + } + &:first-child { + padding-left: 0; + &::before { + display: none; + } + } + &:last-child { + padding-right: 0; + } + &.item01 { + color: #3bbb48; + span { + background-image: url(../../public/static/images/sub/open_ico.svg); + } + } + &.item02 { + color: #909000; + span { + background-image: url(../../public/static/images/sub/change_ico.svg); + } + } + &.item03 { + color: #0191c9; + span { + background-image: url(../../public/static/images/sub/attachment_ico.svg); + } + } + &.item04 { + color: #f16a6a; + span { + background-image: url(../../public/static/images/sub/click_check_ico.svg); + } + } + } + } + .product-edit-btn { + display: flex; + align-items: center; + button { + display: flex; + align-items: center; + span { + width: 13px; + height: 13px; + margin-right: 5px; + background-size: cover; + &.plus { + background: url(../../public/static/images/sub/plus_btn.svg) no-repeat center; + } + &.minus { + background: url(../../public/static/images/sub/minus_btn.svg) no-repeat center; + } + } + } + } + } +} + +// 발전시물레이션 +.chart-wrap { + display: flex; + gap: 20px; + width: 100%; + .sub-table-box { + height: 100%; + } + .chart-inner { + flex: 1; + .chart-box { + margin-bottom: 30px; + } + } + .chart-table-wrap { + display: flex; + flex-direction: column; + flex: none; + width: 650px; + .sub-table-box { + flex: 1; + &:first-child { + margin-bottom: 20px; + } + } + } +} + +.chart-month-table { + table { + table-layout: fixed; + border-collapse: collapse; + border: 1px solid #ecf0f4; + border-radius: 4px; + thead { + th { + padding: 4.5px 0; + border-bottom: 1px solid #ecf0f4; + text-align: center; + font-size: 13px; + color: #45576f; + font-weight: 500; + background-color: #f8f9fa; + } + } + tbody { + td { + font-size: 13px; + color: #45576f; + text-align: center; + padding: 4.5px 0; + } + } + } +} + +.simulation-guide-wrap { + display: flex; + padding: 20px; + .simulation-tit-wrap { + flex: none; + padding-right: 40px; + border-right: 1px solid #eeeeee; + span { + display: block; + position: relative; + padding-left: 60px; + font-size: 15px; + color: #14324f; + font-weight: 600; + &::before { + content: ''; + position: absolute; + top: 50%; + left: 0; + transform: translateY(-50%); + width: 40px; + height: 40px; + background: url(../../public/static/images/sub/simulation_guide.svg) no-repeat center; + background-size: cover; + } + } + } + .simulation-guide-box { + flex: 1; + padding-left: 40px; + dl { + margin-bottom: 25px; + dt { + font-size: 13px; + color: #101010; + font-weight: 600; + margin-bottom: 5px; + } + dd { + font-size: 12px; + color: #45576f; + font-weight: 400; + line-height: 24px; + } + &:last-child { + margin-bottom: 0; + } + } + ul, + ol { + list-style: unset; + } + } +} + +.module-total { + display: flex; + align-items: center; + background-color: #f8f9fa; + padding: 9px 0; + margin-right: 4px; + border: 1px solid #ecf0f4; + border-top: none; + .total-title { + flex: 1; + text-align: center; + font-size: 13px; + color: #344356; + font-weight: 500; + } + .total-num { + flex: none; + width: 121px; + text-align: center; + font-size: 15px; + color: #344356; + font-weight: 500; + } +} + +// 물건상세 +.information-help-wrap { + display: flex; + padding: 24px; + background-color: #f4f4f4; + border-radius: 4px; + margin-bottom: 15px; + .information-help-tit-wrap { + position: relative; + display: flex; + align-items: center; + padding-right: 40px; + border-right: 1px solid #e0e0e3; + .help-tit-icon { + width: 40px; + height: 40px; + border-radius: 50%; + margin-right: 10px; + background: #fff url(../../public/static/images/sub/information_help.svg) no-repeat center; + background-size: 20px 20px; + } + .help-tit { + font-size: 13px; + font-weight: 600; + color: #45576f; + } + } + .information-help-guide { + padding-left: 40px; + span { + display: block; + font-size: 12px; + font-weight: 400; + color: #45576f; + margin-bottom: 7px; + &:last-child { + margin-bottom: 0; + } + } + } +} + +.community-search-warp { + display: flex; + flex-direction: column; + align-items: center; + padding: 10px 0 30px 0; + border-bottom: 1px solid #e5e5e5; + margin-bottom: 24px; + .community-search-box { + position: relative; + display: flex; + align-items: center; + width: 580px; + height: 45px; + padding: 0 45px 0 20px; + margin-bottom: 20px; + border-radius: 2px; + border: 1px solid #101010; + .community-input { + width: 100%; + height: 100%; + font-size: 13px; + font-weight: 400; + color: #101010; + &::placeholder { + color: #c8c8c8; + } + } + .community-search-ico { + position: absolute; + top: 50%; + right: 20px; + transform: translateY(-50%); + flex: none; + width: 21px; + height: 100%; + background: url(../../public/static/images/sub/community_search.svg) no-repeat center; + background-size: 21px 21px; + z-index: 3; + } + } + .community-search-keyword { + font-size: 13px; + font-weight: 400; + color: #45576f; + span { + font-weight: 600; + color: #f16a6a; + } + } +} + +// 자료 다운로드 +.file-down-list { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 14px; + .file-down-item { + display: flex; + align-items: center; + padding: 24px; + border-radius: 4px; + border: 1px solid #e5e5e5; + background: #fff; + transition: all 0.15s ease-in-out; + .file-item-info { + .item-num { + display: inline-block; + padding: 6px 17.5px; + border-radius: 60px; + background-color: #f4f4f7; + font-size: 13px; + font-weight: 600; + color: #101010; + margin-bottom: 15px; + } + .item-name { + font-size: 16px; + color: #101010; + font-weight: 500; + margin-bottom: 13px; + } + .item-date { + font-size: 13px; + font-weight: 400; + color: #344356; + } + } + .file-down-box { + display: flex; + align-items: center; + flex: none; + margin-left: auto; + height: 100%; + .file-down-btn { + width: 36px; + height: 36px; + background: url(../../public/static/images/sub/file_down_btn.svg) no-repeat center; + background-size: cover; + } + } + &:hover { + background-color: #f4f4f7; + } + } +} + +.file-down-nodata { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 148px; + padding: 24px; + border-radius: 4px; + border: 1px solid #e5e5e5; + font-size: 16px; + font-weight: 500; + color: #344356; } //신규물건 등록 -.product-input-wrap{ - display: flex; - align-items: center; - width: 200px; - height: 30px; - background-color: #FAFAFA; - border: 1px solid #EEE; - padding: 0 10px; - input{ - font-size: 13px; - font-weight: 400; - color: #999999; - padding: 0; - width: 100%; - height: 100%; - flex: 1 ; - background-color: inherit; - } - .product-delete{ - flex: none; - display: block; - width: 15px; - height: 100%; - background: url(../../public/static/images/sub/product-del.svg)no-repeat center; - background-size: 15px 15px; - } +.product-input-wrap { + display: flex; + align-items: center; + width: 200px; + height: 30px; + background-color: #fafafa; + border: 1px solid #eee; + padding: 0 10px; + input { + font-size: 13px; + font-weight: 400; + color: #999999; + padding: 0; + width: 100%; + height: 100%; + flex: 1; + background-color: inherit; + } + .product-delete { + flex: none; + display: block; + width: 15px; + height: 100%; + background: url(../../public/static/images/sub/product-del.svg) no-repeat center; + background-size: 15px 15px; + } } @media screen and (max-width: 1800px) { - .canvas-menu-wrap{ - .canvas-menu-inner{ - .canvas-menu-list{ - .canvas-menu-item button{ - .menu-icon{ - margin-right: 5px; - } - } - .canvas-menu-item{ - button{ - padding: 15px 15px; - font-size: 11px; - } - } - } + .canvas-menu-wrap { + .canvas-menu-inner { + .canvas-menu-list { + .canvas-menu-item button { + .menu-icon { + margin-right: 5px; + } } - .canvas-depth2-wrap{ - .canvas-depth2-inner{ - .canvas-depth2-list{ - .canvas-depth2-item{ - button{ - font-size: 11px; - } - } - } - } + .canvas-menu-item { + button { + padding: 15px 15px; + font-size: 11px; + } } - } + } + } + .canvas-depth2-wrap { + .canvas-depth2-inner { + .canvas-depth2-list { + .canvas-depth2-item { + button { + font-size: 11px; + } + } + } + } + } + } } @media screen and (max-width: 1600px) { - .canvas-menu-wrap{ - .canvas-menu-inner{ - .canvas-menu-list{ - .canvas-menu-item button{ - .menu-icon{ - display: none; - } - } - } + .canvas-menu-wrap { + .canvas-menu-inner { + .canvas-menu-list { + .canvas-menu-item button { + .menu-icon { + display: none; + } } - } - .canvas-content{ - .canvas-frame{ - height: calc(100vh - 129.5px); - } - &.active{ - .canvas-frame{ - height: calc(100vh - 179.5px); - } - } - } + } + } + } + .canvas-content { + .canvas-frame { + height: calc(100vh - 129.5px); + } + &.active { + .canvas-frame { + height: calc(100vh - 179.5px); + } + } + } } @media screen and (max-width: 1500px) { - .canvas-menu-wrap{ - .canvas-menu-inner{ - .canvas-menu-list{ - .canvas-menu-item{ - button{ - padding: 15px 10px; - font-size: 10px; - } - } - } - .canvas-side-btn-wrap{ - .btn-from{ - gap: 3px; - } - .vertical-horizontal{ - margin-right: 3px; - min-width: 150px; - } - .select-box{ - width: 100px; - margin: 0 3px; - } - .size-control{ - width: 90px; - margin: 0 3px; - } - } + .canvas-menu-wrap { + .canvas-menu-inner { + .canvas-menu-list { + .canvas-menu-item { + button { + padding: 15px 10px; + font-size: 10px; + } } + } + .canvas-side-btn-wrap { + .btn-from { + gap: 3px; + } + .vertical-horizontal { + margin-right: 3px; + min-width: 150px; + } + .select-box { + width: 100px; + margin: 0 3px; + } + .size-control { + width: 90px; + margin: 0 3px; + } + } } - .sub-header{ - .sub-header-inner{ - .sub-header-title{ - font-size: 15px; + } + .sub-header { + .sub-header-inner { + .sub-header-title { + font-size: 15px; + } + .sub-header-title-wrap { + .title-item { + a { + .icon { + width: 20px; + height: 20px; } - .sub-header-title-wrap{ - .title-item{ - a{ - .icon{ - width: 20px; - height: 20px; - } - } - } - } - } - } - + } + } + } + } + } } diff --git a/src/styles/_layout.scss b/src/styles/_layout.scss index a549ad0e..858ab351 100644 --- a/src/styles/_layout.scss +++ b/src/styles/_layout.scss @@ -207,6 +207,7 @@ header{ .select-box{ min-width: 165px; margin-right: 8px; + height: 30px; >div{ width: 100%; } diff --git a/src/styles/_main.scss b/src/styles/_main.scss index 0733f285..5da1350d 100644 --- a/src/styles/_main.scss +++ b/src/styles/_main.scss @@ -155,6 +155,7 @@ .product-item-content{ margin-top: 30px; overflow: hidden; + height: 100%; .recently-list{ .recently-item{ border: 1px solid #F2F2F2; @@ -208,6 +209,25 @@ } } } + .recently-no-data{ + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + h3{ + font-size: 16px; + color: #101010; + font-weight: 600; + margin-bottom: 5px; + } + p{ + font-size: 12px; + color: #666; + font-weight: 400; + margin-bottom: 10px; + } + } .notice-box{ height: 100%; overflow-y: auto; diff --git a/src/styles/_modal.scss b/src/styles/_modal.scss index 50e2a4eb..296deefe 100644 --- a/src/styles/_modal.scss +++ b/src/styles/_modal.scss @@ -4,2004 +4,2233 @@ $pop-bold-weight: 500; $pop-normal-size: 12px; $alert-color: #101010; -@keyframes mountpop{ - from{opacity: 0; scale: 0.95;} - to{opacity: 1; scale: 1;} +@keyframes mountpop { + from { + opacity: 0; + scale: 0.95; + } + to { + opacity: 1; + scale: 1; + } } -@keyframes unmountpop{ - from{opacity: 1; scale: 1;} - to{opacity: 0; scale: 0.95;} +@keyframes unmountpop { + from { + opacity: 1; + scale: 1; + } + to { + opacity: 0; + scale: 0.95; + } } -.normal-font{ - font-size: 12px; - font-weight: 400; - color: #fff; +.normal-font { + font-size: 12px; + font-weight: 400; + color: #fff; } -.bold-font{ - font-size: 12px; - font-weight: 500; - color: #fff; +.bold-font { + font-size: 12px; + font-weight: 500; + color: #fff; } -.modal-pop-wrap{ - position: fixed; - top: 0; - left: 0; - width: 100%; - height: -webkit-fit-content; - height: -moz-fit-content; - height: fit-content; - border: 1px solid #000; - border-radius: 4px; - background-color: #272727; - z-index: 9999999; - &.xsm{ - width: 200px; +.modal-pop-wrap { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: -webkit-fit-content; + height: -moz-fit-content; + height: fit-content; + border: 1px solid #000; + border-radius: 4px; + background-color: #272727; + z-index: 9999999; + &.xsm { + width: 200px; + } + &.xxxm { + width: 240px; + } + &.xxm { + width: 270px; + } + &.xm { + width: 300px; + } + &.ssm { + width: 380px; + } + &.sm { + width: 580px; + } + &.r { + width: 400px; + } + &.lr { + width: 440px; + } + &.lr-2 { + width: 450px; + } + &.lrr { + width: 480px; + } + &.ml { + width: 530px; + } + &.l-2 { + width: 640px; + } + &.lx-2 { + width: 740px; + } + &.lx { + width: 770px; + } + &.l { + width: 800px; + } + &.ll { + width: 900px; + } + &.mount { + animation: mountpop 0.17s ease-in-out forwards; + } + &.unmount { + animation: unmountpop 0.17s ease-in-out forwards; + } + &.alert { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: transparent; + border: none; + .modal-head { + background-color: transparent; + padding: 0 0 8px; + .modal-close { + width: 20px; + height: 20px; + background: url(../../public/static/images/canvas/alert_close.svg) no-repeat center; + } } - &.xxxm{ - width: 240px; - } - &.xxm{ - width: 270px; - } - &.xm{ - width: 300px; - } - &.ssm{ - width: 380px; - } - &.sm{ - width: 580px; - } - &.r{ - width: 400px; - } - &.lr{ - width: 440px; - } - &.lr-2{ - width: 450px; - } - &.lrr{ - width: 480px; - } - &.ml{ - width: 530px; - } - &.l-2{ - width: 640px; - } - &.lx-2{ - width: 740px; - } - &.lx{ - width: 770px; - } - &.l{ - width: 800px; - } - &.mount{ - animation: mountpop .17s ease-in-out forwards; - } - &.unmount{ - animation: unmountpop .17s ease-in-out forwards; - } - &.alert{ - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background-color: transparent; - border: none; - .modal-head{ - background-color: transparent; - padding: 0 0 8px; - .modal-close{ - width: 20px; - height: 20px; - background: url(../../public/static/images/canvas/alert_close.svg)no-repeat center; - } - } - .modal-body{ - background-color: #fff; - padding: 22px; - border-radius: 4px; - border: 1px solid #101010; - color: $alert-color; - .alert-title{ - font-size: 13px; - font-weight: 700; - color: $alert-color; - margin-bottom: 15px; - } - } - } -} -.modal-head{ - display: flex; - align-items: center; - padding: 10px 24px; - background-color: #000; - // overflow: hidden; - h1.title{ + .modal-body { + background-color: #fff; + padding: 22px; + border-radius: 4px; + border: 1px solid #101010; + color: $alert-color; + .alert-title { font-size: 13px; - color: $pop-color; font-weight: 700; - } - .modal-close{ - margin-left: auto; - color: transparent; - font-size: 0; - width: 10px; - height: 10px; - background: url(../../public/static/images/canvas/modal_close.svg)no-repeat center; - } -} -.modal-body{ - padding: 24px; - .modal-btn-wrap{ - display: flex; - align-items: center; - gap: 5px; - button{ - flex: 1; - } - &.sub{ - button{ - flex: 1 1 auto; - padding: 0; - } - margin-bottom: 14px; - } - } - .modal-check-btn-wrap{ - margin-top: 15px; - .check-wrap-title{ - font-size: $pop-normal-size; - color: $pop-color; - font-weight: 600; - &.light{ - font-weight: $pop-normal-weight; - } - } - .flex-check-box{ - display: flex; - flex-wrap: wrap; - gap: 10px; - margin-top: 15px; - &.for2{ - justify-content: flex-end; - button{ - width: calc(50% - 5px); - } - &.btn{ - gap: 5px; - button{ - width: calc(50% - 2.5px); - } - } - } - &.for-line{ - button{ - flex: 1; - } - } - } - } - .outer-line-wrap{ - border-top: 1px solid #3C3C3C; - margin-top: 10px; - padding-top: 15px; - margin-bottom: 15px; - > div{ - margin-bottom: 15px; - &:last-child{ - margin-bottom: 0; - } - } - } - .modal-guide{ - display: block; - font-size: $pop-normal-size; color: $alert-color; - font-weight: $pop-normal-weight; + margin-bottom: 15px; + } } + } } - -.adsorption-point{ +.modal-head { + display: flex; + align-items: center; + padding: 10px 24px; + background-color: #000; + // overflow: hidden; + h1.title { + font-size: 13px; + color: $pop-color; + font-weight: 700; + } + .modal-close { + margin-left: auto; + color: transparent; + font-size: 0; + width: 10px; + height: 10px; + background: url(../../public/static/images/canvas/modal_close.svg) no-repeat center; + } +} +.modal-body { + padding: 24px; + .modal-btn-wrap { display: flex; align-items: center; - background-color: #3A3A3A; - border-radius: 3px; - padding-left: 11px; - overflow: hidden; - transition: all 0.17s ease-in-out; - span{ - font-size: $pop-normal-size; - color: #898989; + gap: 5px; + button { + flex: 1; } - i{ - display: flex; - align-items: center; - padding: 0 7px; - margin-left: auto; - height: 100%; - font-size: 13px; - color: #898989; + &.sub { + button { + flex: 1 1 auto; + padding: 0; + } + margin-bottom: 14px; } - &.act{ - i{ - color: $pop-color; - background-color: #1083E3; + } + .modal-check-btn-wrap { + margin-top: 15px; + .check-wrap-title { + font-size: $pop-normal-size; + color: $pop-color; + font-weight: 600; + &.light { + font-weight: $pop-normal-weight; + } + } + .flex-check-box { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 15px; + &.for2 { + justify-content: flex-end; + button { + width: calc(50% - 5px); } + &.btn { + gap: 5px; + button { + width: calc(50% - 2.5px); + } + } + } + &.for-line { + button { + flex: 1; + } + } } + } + .outer-line-wrap { + border-top: 1px solid #3c3c3c; + margin-top: 10px; + padding-top: 15px; + margin-bottom: 15px; + > div { + margin-bottom: 15px; + &:last-child { + margin-bottom: 0; + } + } + } + .modal-guide { + display: block; + font-size: $pop-normal-size; + color: $alert-color; + font-weight: $pop-normal-weight; + } +} + +.adsorption-point { + display: flex; + align-items: center; + background-color: #3a3a3a; + border-radius: 3px; + padding-left: 11px; + overflow: hidden; + transition: all 0.17s ease-in-out; + span { + font-size: $pop-normal-size; + color: #898989; + } + i { + display: flex; + align-items: center; + padding: 0 7px; + margin-left: auto; + height: 100%; + font-size: 13px; + color: #898989; + } + &.act { + i { + color: $pop-color; + background-color: #1083e3; + } + } } // grid-option -.grid-check-form{ +.grid-check-form { + display: flex; + align-items: center; + gap: 15px; + padding-bottom: 15px; + &.border { + border-bottom: 1px solid #424242; + } +} +.grid-option-overflow { + max-height: 350px; + overflow-y: auto; + &::-webkit-scrollbar { + width: 4px; + background-color: transparent; + } + &::-webkit-scrollbar-thumb { + background-color: #d9d9d9; + } + &::-webkit-scrollbar-track { + background-color: transparent; + } +} +.grid-option-wrap { + .grid-option-box { display: flex; align-items: center; - gap: 15px; - padding-bottom: 15px; - &.border{ - border-bottom: 1px solid #424242; - } -} -.grid-option-wrap{ - .grid-option-box{ - display: flex; - align-items: center; - background-color: transparent; - border: 1px solid #3D3D3D; - border-radius: 2px; - padding: 15px 10px; - gap: 20px; - margin-bottom: 10px; - .grid-input-form{ - display: flex; - align-items: center; - span{ - flex: none; - font-size: $pop-normal-size; - color: $pop-color; - font-weight: $pop-bold-weight; - } - .input-grid{ - width: 54px; - input{ - width: 100%; - } - } - } - &:last-child{ - margin-bottom: 0; + background-color: transparent; + border: 1px solid #3d3d3d; + border-radius: 2px; + padding: 15px 10px; + gap: 20px; + margin-bottom: 10px; + .grid-input-form { + display: flex; + align-items: center; + span { + flex: none; + font-size: $pop-normal-size; + color: $pop-color; + font-weight: $pop-bold-weight; + } + .input-grid { + width: 54px; + input { + width: 100%; } + } } -} -.select-form{ - .sort-select{width: 100%;} -} -.grid-select{ + &:last-child { + margin-bottom: 0; + } + } + .grid-option-block-form { flex: 1; - &.no-flx{ - flex: unset; - } - .sort-select{ - width: 100%; - background-color: #313131; - min-width: auto; - font-size: 12px; - border: none; - p{ - font-size: 12px; - } - > ul{ - border: none; - } - } - &.right{ - p{ - text-align: right; - } - ul{ - li{ - justify-content: flex-end; - } - } - } + } } -.grid-btn-wrap{ - padding-top: 15px; - text-align: right; - button{ - padding: 0 10px; +.select-form { + .sort-select { + width: 100%; + } +} +.grid-select { + flex: 1; + &.no-flx { + flex: unset; + } + .sort-select { + width: 100%; + background-color: #313131; + min-width: auto; + font-size: 12px; + border: none; + p { + font-size: 12px; } + > ul { + border: none; + } + } + &.right { + p { + text-align: right; + } + ul { + li { + justify-content: flex-end; + } + } + } +} +.grid-btn-wrap { + padding-top: 15px; + text-align: right; + button { + padding: 0 10px; + } } // grid copy -.grid-option-tit{ - font-size: $pop-normal-size; - color: $pop-color; - font-weight: $pop-normal-weight; - padding-bottom: 15px; - +.grid-option-tit { + font-size: $pop-normal-size; + color: $pop-color; + font-weight: $pop-normal-weight; + padding-bottom: 15px; } -.grid-direction{ - display: flex; - align-items: center; - gap: 5px; - flex: 1; +.grid-direction { + display: flex; + align-items: center; + gap: 5px; + flex: 1; } -.direction{ - width: 22px; - height: 22px; - background-color: #757575; - background-image: url(../../public/static/images/canvas/grid_option_arr.svg); - background-repeat: no-repeat; - background-position: center; - background-size: 16px 15px; - border-radius: 50%; - transition: all .15s ease-in-out; - opacity: 0.6; - &.down{transform: rotate(180deg);} - &.left{transform: rotate(-90deg);} - &.right{transform: rotate(90deg);} - &:hover, - &.act{ - opacity: 1; - } +.direction { + width: 22px; + height: 22px; + background-color: #757575; + background-image: url(../../public/static/images/canvas/grid_option_arr.svg); + background-repeat: no-repeat; + background-position: center; + background-size: 16px 15px; + border-radius: 50%; + transition: all 0.15s ease-in-out; + opacity: 0.6; + &.down { + transform: rotate(180deg); + } + &.left { + transform: rotate(-90deg); + } + &.right { + transform: rotate(90deg); + } + &:hover, + &.act { + opacity: 1; + } } // grid-move -.move-form{ - width: 100%; - p{ - font-size: $pop-normal-size; - color: $pop-color; - font-weight: $pop-bold-weight; - } +.move-form { + width: 100%; + p { + font-size: $pop-normal-size; + color: $pop-color; + font-weight: $pop-bold-weight; + } } -.input-move-wrap{ - display: flex; - align-items: center; - gap: 5px; - span{ - color: $pop-color; - font-size: $pop-normal-size; - } - .input-move{ - width: 130px; - input{ - width: 100%; - } +.input-move-wrap { + display: flex; + align-items: center; + gap: 5px; + span { + color: $pop-color; + font-size: $pop-normal-size; + } + .input-move { + width: 130px; + input { + width: 100%; } + } } -.direction-move-wrap{ - flex: none; - display: grid; - grid-template-columns: 1fr 1fr; - gap: 5px; - margin-left: auto; +.direction-move-wrap { + flex: none; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 5px; + margin-left: auto; } // 배치면 초기 설정 -.placement-table{ - table{ - table-layout: fixed; - tr{ - th{ - font-size: $pop-normal-size; - color: $pop-color; - font-weight: $pop-bold-weight; - padding: 18px 0; - border-bottom: 1px solid #424242; - vertical-align: middle; - .tip-wrap{ - display: flex; - align-items: center; - } - } - td{ - font-size: $pop-normal-size; - color: $pop-color; - border-bottom: 1px solid #424242; - padding: 18px 0 18px 20px; - vertical-align: middle; - .flex-box{ - display: flex; - align-items: center; - } - } - &:first-child{ - td, - th{ - padding-top: 0; - } - } - } - } - .tooltip{ - position: relative; - display: block; - width: 15px; - height: 15px; - margin-left: 5px; - background: url(../../public/static/images/canvas/pop_tip.svg)no-repeat center; - background-size: cover; - } - &.light{ - padding: 0; - th,td{ - color: $alert-color; - border-bottom: none; - border-top: 1px solid #EFEFEF; - } - th{ - padding: 14px 0; - } - tr{ - &:first-child{ - td, - th{ - padding-top: 14px; - } - } - &:last-child{ - td, - th{ - padding-bottom: 0px; - } - } - } - } -} - -.pop-form-radio{ - display: flex; - align-items: center; - gap: 10px; -} -.placement-option{ - display: flex; - align-items: center; - gap: 20px; -} -.select-wrap{ - .sort-select{ - width: 100%; - } -} -.flex-ment{ - display: flex; - align-items: center; - gap: 5px; - span{ +.placement-table { + table { + table-layout: fixed; + tr { + th { font-size: $pop-normal-size; color: $pop-color; - font-weight: $pop-normal-weight; + font-weight: $pop-bold-weight; + padding: 18px 0; + border-bottom: 1px solid #424242; + vertical-align: middle; + .tip-wrap { + display: flex; + align-items: center; + } + } + td { + font-size: $pop-normal-size; + color: $pop-color; + border-bottom: 1px solid #424242; + padding: 18px 0 18px 20px; + vertical-align: middle; + .flex-box { + display: flex; + align-items: center; + } + } + &:first-child { + td, + th { + padding-top: 0; + } + } } - + } + .tooltip { + position: relative; + display: block; + width: 15px; + height: 15px; + margin-left: 5px; + background: url(../../public/static/images/canvas/pop_tip.svg) no-repeat center; + background-size: cover; + } + &.light { + padding: 0; + th, + td { + color: $alert-color; + border-bottom: none; + border-top: 1px solid #efefef; + } + th { + padding: 14px 0; + } + tr { + &:first-child { + td, + th { + padding-top: 14px; + } + } + &:last-child { + td, + th { + padding-bottom: 0px; + } + } + } + } } -.img-edit-wrap{ - flex: none; - .img-edit-btn{ - display: flex; - align-items: center; - height: 30px; - padding: 0 10px; - font-size: 12px; - font-weight: 400; - color: #101010; - background-color: #fff; - border-radius: 2px; - cursor: pointer; - transition: all .15s ease-in-out; - .img-edit{ - width: 16px; - height: 16px; - background: url(../../public/static/images/canvas/img_edit_ico.svg)no-repeat center; - background-size: cover; - margin-right: 5px; - } - &:hover{ - background-color: #ebebeb; - } - } +// 2024-12-11 +// .placement-roof-btn-wrap{ +// display: flex; +// align-items: center; +// margin-left: auto; +// max-width: 250px; +// } + +.pop-form-radio { + display: flex; + align-items: center; + gap: 10px; } -.img-name-wrap{ +.placement-option { + display: flex; + align-items: center; + gap: 20px; +} +.select-wrap { + .sort-select { + width: 100%; + } +} +.flex-ment { + display: flex; + align-items: center; + gap: 5px; + span { + font-size: $pop-normal-size; + color: $pop-color; + font-weight: $pop-normal-weight; + } +} + +.img-edit-wrap { + flex: none; + .img-edit-btn { display: flex; align-items: center; - width: 100%; - margin-left: 10px; - input{ - flex: 1; - + height: 30px; + padding: 0 10px; + font-size: 12px; + font-weight: 400; + color: #101010; + background-color: #fff; + border-radius: 2px; + cursor: pointer; + transition: all 0.15s ease-in-out; + .img-edit { + width: 16px; + height: 16px; + background: url(../../public/static/images/canvas/img_edit_ico.svg) no-repeat center; + background-size: cover; + margin-right: 5px; } - .img-check{ - flex: none; - width: 18px; - height: 18px; - margin-left: 5px; - background-repeat: no-repeat; - background-position: center; - background-size: cover; - background-image: url(../../public/static/images/canvas/img_check_fail.svg); + &:hover { + background-color: #ebebeb; } + } +} +.img-name-wrap { + display: flex; + align-items: center; + width: 100%; + margin-left: 10px; + input { + flex: 1; + } + .img-check { + flex: none; + width: 18px; + height: 18px; + margin-left: 5px; + background-repeat: no-repeat; + background-position: center; + background-size: cover; + background-image: url(../../public/static/images/canvas/img_check_fail.svg); + } } -.for-address{ - input{ - flex: 1; +.for-address { + input { + flex: 1; + } + .check-address { + flex: none; + width: 18px; + height: 18px; + margin-left: 5px; + background-repeat: no-repeat; + background-position: center; + background-size: cover; + &.fail { + background-image: url(../../public/static/images/canvas/img_check_fail.svg); } - .check-address{ - flex: none; - width: 18px; - height: 18px; - margin-left: 5px; - background-repeat: no-repeat; - background-position: center; - background-size: cover; - &.fail{background-image: url(../../public/static/images/canvas/img_check_fail.svg);} - &.success{background-image: url(../../public/static/images/canvas/img_check_success.svg);} + &.success { + background-image: url(../../public/static/images/canvas/img_check_success.svg); } + } } // 외벽선 그리기 -.outline-wrap{ - padding: 24px 0; - border-top: 1px solid #424242; - - .outline-inner{ - display: flex; - align-items: center; - margin-bottom: 14px; - &:last-child{ - margin-bottom: 0; - } - .outline-form{ - // width: 50%; - margin-right: 15px; - } - } - &:last-child{ - border-bottom: 1px solid #424242; - } -} -.outline-form{ +.outline-wrap { + padding: 24px 0; + border-top: 1px solid #424242; + + .outline-inner { display: flex; align-items: center; - - span{ - width: 60px; - flex: none; - font-size: $pop-normal-size; - font-weight: $pop-bold-weight; - color: $pop-color; - margin-right: 10px; - &.thin{ - width: auto; - font-weight: $pop-normal-weight; - margin-right: 0; - } + margin-bottom: 14px; + &:last-child { + margin-bottom: 0; } + .outline-form { + // width: 50%; + margin-right: 15px; + } + } + &:last-child { + border-bottom: 1px solid #424242; + } +} +.outline-form { + display: flex; + align-items: center; - .reset-btn{ - flex: none; - width: 30px; - height: 30px; - background: transparent; - border: 1px solid #484848; - border-radius: 2px; - margin-left: 5px; - background-image: url(../../public/static/images/canvas/reset_ico.svg); - background-repeat: no-repeat; - background-size: 12px 12px; - background-position: center; - } - &:last-child{ - margin-right: 0; + span { + width: 60px; + flex: none; + font-size: $pop-normal-size; + font-weight: $pop-bold-weight; + color: $pop-color; + margin-right: 10px; + &.thin { + width: auto; + font-weight: $pop-normal-weight; + margin-right: 0; } + } + + .reset-btn { + flex: none; + width: 30px; + height: 30px; + background: transparent; + border: 1px solid #484848; + border-radius: 2px; + margin-left: 5px; + background-image: url(../../public/static/images/canvas/reset_ico.svg); + background-repeat: no-repeat; + background-size: 12px 12px; + background-position: center; + } + &:last-child { + margin-right: 0; + } } -.cul-wrap{ +.cul-wrap { + display: flex; + .outline-box { + width: 50%; + margin-right: 15px; + .outline-form { + width: 100%; + margin-bottom: 14px; + margin-right: 0; + &:last-child { + margin-bottom: 0; + } + } + } + .cul-box { display: flex; - .outline-box{ - width: 50%; - margin-right: 15px; - .outline-form{ - width: 100%; - margin-bottom: 14px; - margin-right: 0; - &:last-child{ - margin-bottom: 0; - } - } - } - .cul-box{ - display: flex; - align-items: center; - justify-content: center; - width: 50%; - background-color: #3D3D3D; - border-radius: 2px ; - } + align-items: center; + justify-content: center; + width: 50%; + background-color: #3d3d3d; + border-radius: 2px; + } } // 외벽선 속성 설정 -.properties-guide{ - font-size: $pop-normal-size; - color: #AAA; - font-weight: $pop-normal-weight; - margin-bottom: 14px; +.properties-guide { + font-size: $pop-normal-size; + color: #aaa; + font-weight: $pop-normal-weight; + margin-bottom: 14px; } -.setting-tit{ - font-size: 13px; - color: $pop-color; - font-weight: $pop-bold-weight; - margin-bottom: 10px; +.setting-tit { + font-size: 13px; + color: $pop-color; + font-weight: $pop-bold-weight; + margin-bottom: 10px; } -.properties-setting-wrap{ - &.outer{ - margin-top: 24px; - } - .setting-btn-wrap{ - display: flex; - align-items: center; - padding: 14px 0; - border-top: 1px solid #424242; - border-bottom: 1px solid #424242; - .setting-btn{ - display: block; - width: 100%; - height: 40px; - font-size: 13px; - color: #fff; - font-weight: 700; - border-radius: 2px; - transition: all .15s ease-in-out; - &.green{ - background-color: #305941; - border: 1px solid #45CD7D; - &:hover{ - background-color: #3a6b4e; - } - } - &.blue{ - background-color: #2E5360; - border: 1px solid #3FBAE6; - &:hover{ - background-color: #365f6e; - } - } - } - } -} - -// 지붕형상 설정 -.roof-shape-menu{ - display: grid; - grid-template-columns: 1fr 1fr 1fr 1fr; - grid-template-rows: 1fr 1fr; - gap: 24px 10px; - margin-bottom: 24px; - .shape-box{ - display: flex; - align-items: center; - justify-content: center; - width: 100%; - padding: 13px; - background-color: #3D3D3D; - transition: background .15s ease-in-out; - img{ - max-width: 100%; - } - } - .shape-title{ - font-size: $pop-normal-size; - font-weight: $pop-bold-weight; - color: $pop-color; - margin-top: 10px; - text-align: center; - transition: color .15s ease-in-out; - } - .shape-menu-box{ - &.act, - &:hover{ - .shape-box{background-color: #008BFF;} - .shape-title{color: #008BFF;} - } - } -} - -.setting-box{ +.properties-setting-wrap { + &.outer { + margin-top: 24px; + } + .setting-btn-wrap { + display: flex; + align-items: center; padding: 14px 0; border-top: 1px solid #424242; border-bottom: 1px solid #424242; -} -.padding-form{ - padding-left: 23px; -} -.discrimination-box{ - padding: 16px 12px; - border: 1px solid #3D3D3D; - border-radius: 2px; + .setting-btn { + display: block; + width: 100%; + height: 40px; + font-size: 13px; + color: #fff; + font-weight: 700; + border-radius: 2px; + transition: all 0.15s ease-in-out; + &.green { + background-color: #305941; + border: 1px solid #45cd7d; + &:hover { + background-color: #3a6b4e; + } + } + &.blue { + background-color: #2e5360; + border: 1px solid #3fbae6; + &:hover { + background-color: #365f6e; + } + } + } + } } -.modal-bottom-border-bx{ - margin-top: 24px; - padding-bottom: 14px; - border-bottom: 1px solid #424242; +// 지붕형상 설정 +.roof-shape-menu { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; + grid-template-rows: 1fr 1fr; + gap: 24px 10px; + margin-bottom: 24px; + .shape-box { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + padding: 13px; + background-color: #3d3d3d; + transition: background 0.15s ease-in-out; + img { + max-width: 100%; + } + } + .shape-title { + font-size: $pop-normal-size; + font-weight: $pop-bold-weight; + color: $pop-color; + margin-top: 10px; + text-align: center; + transition: color 0.15s ease-in-out; + } + .shape-menu-box { + &.act, + &:hover { + .shape-box { + background-color: #008bff; + } + .shape-title { + color: #008bff; + } + } + } +} + +.setting-box { + padding: 14px 0; + border-top: 1px solid #424242; + border-bottom: 1px solid #424242; +} +.padding-form { + padding-left: 23px; +} +.discrimination-box { + padding: 16px 12px; + border: 1px solid #3d3d3d; + border-radius: 2px; +} + +.modal-bottom-border-bx { + margin-top: 24px; + padding-bottom: 14px; + border-bottom: 1px solid #424242; } // 처마∙케라바 변경 -.eaves-keraba-table{ - display: table; - border-collapse: collapse; - .eaves-keraba-item{ - display: table-row; - .eaves-keraba-th, - .eaves-keraba-td{ - font-size: $pop-normal-size; - color: $pop-color; - font-weight: $pop-normal-weight; - display: table-cell; - vertical-align: middle; - padding-bottom: 14px; - } - .eaves-keraba-td{ - padding-left: 10px; - } - .eaves-keraba-ico{ - display: flex; - align-items: center; - justify-content: center; - padding: 5px; - background-color: #3D3D3D; - border: 1px solid #3D3D3D; - border-radius: 2px; - cursor: pointer; - &.act{ - border: 1px solid #ED0004; - } - } - &:last-child{ - .eaves-keraba-th, - .eaves-keraba-td{ - padding-bottom: 0; - } - } +.eaves-keraba-table { + display: table; + border-collapse: collapse; + .eaves-keraba-item { + display: table-row; + .eaves-keraba-th, + .eaves-keraba-td { + font-size: $pop-normal-size; + color: $pop-color; + font-weight: $pop-normal-weight; + display: table-cell; + vertical-align: middle; + padding-bottom: 14px; } + .eaves-keraba-td { + padding-left: 10px; + } + .eaves-keraba-ico { + display: flex; + align-items: center; + justify-content: center; + padding: 5px; + background-color: #3d3d3d; + border: 1px solid #3d3d3d; + border-radius: 2px; + cursor: pointer; + &.act { + border: 1px solid #ed0004; + } + } + &:last-child { + .eaves-keraba-th, + .eaves-keraba-td { + padding-bottom: 0; + } + } + } } -.guide{ - font-size: $pop-normal-size; - font-weight: $pop-normal-weight; - color: $pop-color; - margin-bottom: 24px; - &.sm{ - margin-bottom: 15px; - } - span{ - display: block; - } +.guide { + font-size: $pop-normal-size; + font-weight: $pop-normal-weight; + color: $pop-color; + margin-bottom: 24px; + &.sm { + margin-bottom: 15px; + } + span { + display: block; + } } // 지붕면 할당 -.allocation-select-wrap{ +.allocation-select-wrap { + display: flex; + align-items: center; + padding-bottom: 14px; + border-bottom: 1px solid #424242; + margin-bottom: 14px; + span { + font-size: $pop-normal-size; + color: $pop-color; + font-weight: $pop-bold-weight; + margin-right: 10px; + } + .allocation-edit { display: flex; align-items: center; - padding-bottom: 14px; - border-bottom: 1px solid #424242; - margin-bottom: 14px; - span{ - font-size: $pop-normal-size; - color: $pop-color; - font-weight: $pop-bold-weight; - margin-right: 10px; - } - .allocation-edit{ - display: flex; - align-items: center; - height: 30px; - padding: 0 10px; - margin-left: 5px; - font-size: $pop-normal-size; - color: $pop-color; - font-weight: $pop-normal-weight; - border: 1px solid #484848; - background-color: #323234; - i{ - display: block; - width: 12px; - height: 12px; - margin-right: 5px; - background: url(../../public/static/images/canvas/allocation_edit.svg)no-repeat center; - background-size: cover; - } + height: 30px; + padding: 0 10px; + margin-left: 5px; + font-size: $pop-normal-size; + color: $pop-color; + font-weight: $pop-normal-weight; + border: 1px solid #484848; + background-color: #323234; + i { + display: block; + width: 12px; + height: 12px; + margin-right: 5px; + background: url(../../public/static/images/canvas/allocation_edit.svg) no-repeat center; + background-size: cover; } + } } -.block-box{ - display: flex; - align-items: center; +.block-box { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 10px; + .flex-ment { gap: 10px; - margin-bottom: 10px; - .flex-ment{ - gap: 10px; - .dec{ - text-decoration: underline; - } - .delete{ - display: block; - width: 15px; - height: 15px; - background: url(../../public/static/images/canvas/allocation_delete.svg)no-repeat center; - background-size: cover; - } + .dec { + text-decoration: underline; } - &:last-child{ - margin-bottom: 0; + .delete { + display: block; + width: 15px; + height: 15px; + background: url(../../public/static/images/canvas/allocation_delete.svg) no-repeat center; + background-size: cover; } + } + &:last-child { + margin-bottom: 0; + } } -.icon-btn-wrap{ - flex: 1; +.icon-btn-wrap { + flex: 1; + display: flex; + align-items: center; + gap: 5px; + button { display: flex; align-items: center; - gap: 5px; - button{ - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 30px; - font-size: $pop-normal-size; - font-weight: $pop-normal-weight; - color: $pop-color; - border: 1px solid #646464; - border-radius: 2px; - padding: 0 10px; - transition: all .15s ease-in-out; - i{ - height: 15px; - display: block; - margin-left: 10px; - background-repeat: no-repeat; - background-position: center; - background-size: cover; - transition: all .15s ease-in-out; - &.allocation01{ - background-image: url(../../public/static/images/canvas/allocation_icon01_white.svg); - width: 15px; - } - &.allocation02{ - background-image: url(../../public/static/images/canvas/allocation_icon02_white.svg); - width: 18px; - } - } - &.act, - &:hover{ - color: #101010; - border: 1px solid #101010; - background-color: #fff; - i{ - &.allocation01{ - background-image: url(../../public/static/images/canvas/allocation_icon01_black.svg); - } - &.allocation02{ - background-image: url(../../public/static/images/canvas/allocation_icon02_black.svg); - } - } - } - } -} - -// 경사설정 -.slope-wrap{ - padding-bottom: 24px; - border-bottom: 1px solid #424242; -} - -// 면형상 배치 -.plane-frame-wrap{ - display: flex; - gap: 10px; - .plane-shape-wrap{ - flex: none; - width: 73px; - } -} - -.plane-shape-menu{ - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 5px; - .shape-menu-box{ - border-radius: 2px; - width: 34px; - height: 34px; - background-color: #373737; - border: 1px solid #676767; - transition: background .15s ease-in-out, border .15s ease-in-out; - .shape-box{ - display: flex; - justify-content: center; - align-items: center; - position: relative; - width: 100%; - height: 100%; - border-radius: 2px; - } - &.act, - &:hover{ - border-color: #008BFF; - background-color: #008BFF; - } - } -} - -.shape-library{ - display: flex; - flex-direction: column; - align-items: center; justify-content: center; - gap: 5px; - padding-top: 5px; - .library-btn{ - width: 100%; - height: 34px; - border: 1px solid #6C6C6C; - border-radius: 2px; - background-color: #373737; - background-repeat: no-repeat; - background-position: center; - transition: all .15s ease-in-out; - &.ico01{background-image: url(../../public/static/images/canvas/shape_labrary01.svg); background-size: 19px 18px;} - &.ico02{background-image: url(../../public/static/images/canvas/shape_labrary02.svg); background-size: 15px 20px;} - &.ico03{background-image: url(../../public/static/images/canvas/shape_labrary03.svg); background-size: 19px 16px;} - &:hover{ - border-color: #1083E3; - background-color: #1083E3; - } - } -} - -.plane-detail-wrap{ - display: flex; - flex-direction: column; - flex: 1; -} -.plane-shape-wrapper{ - flex: 1; - display: flex; - flex-direction: column; - gap: 10px; - .plane-box{ - width: 100%; - padding: 10px 18px; - border-radius: 2px; - background-color: #313131; - border: 1px solid #484848; - .plane-box-tit{ - font-size: $pop-normal-size; - font-weight: 600; - color: $pop-color; - margin-bottom: 10px; - } - &.shape-box{ - .shape-box-inner{ - display: flex; - gap:15px; - min-height: 140px; - .shape-img{ - position: relative; - flex: none; - width: 150px; - background-color: #fff; - border-radius: 2px; - img{ - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } - } - .shape-data{ - flex: 1; - .eaves-keraba-table{ - .eaves-keraba-item{ - .eaves-keraba-th, - .eaves-keraba-td{ - padding-bottom: 10px; - } - &:last-child{ - .eaves-keraba-th, - .eaves-keraba-td{ - padding-bottom: 0px; - } - } - } - } - } - } - } - &.direction-box{ - flex: 1; - display: flex; - flex-direction: column; - .plane-direction-box{ - flex: 1; - display: flex; - align-items: center; - justify-content: center; - width: 100%; - } - } - } -} -.plane-direction{ - width: 150px; - position: relative; - height: 120px; - span{ - position: absolute; - font-size: 12px; - font-weight: 500; - color: #B1B1B1; - &.top{top: 0; left: 50%; transform: translateX(-50%);} - &.right{top: 50%; right: 0; transform: translateY(-50%);} - &.bottom{bottom: 0; left: 50%; transform: translateX(-50%);} - &.left{top: 50%; left: 0; transform: translateY(-50%);} - } - .plane-btn{ - position: absolute; - width: 28px; - height: 28px; - background-color: #777777; - background-image: url(../../public/static/images/canvas/plane_arr.svg); - background-size: 12px 13px; - background-repeat: no-repeat; - background-position: center; - border-radius: 50%; - transition: all .15s ease-in-out; - &.up{top: 22px; left: 50%; transform: translateX(-50%);} - &.right{top: 50%; right: 32px; transform: translateY(-50%) rotate(90deg);} - &.down{bottom: 22px; left: 50%; transform: translateX(-50%) rotate(180deg);} - &.left{top: 50%; left: 32px; transform: translateY(-50%) rotate(270deg);} - &:hover, - &.act{ - background-color: #fff; - background-image: url(../../public/static/images/canvas/plane_arr_act.svg); - } - } -} - -.plane-tab-guide{ + width: 100%; + height: 30px; font-size: $pop-normal-size; font-weight: $pop-normal-weight; color: $pop-color; - margin-top: 10px; -} -.plane-shape-btn{ - padding-top: 10px; - margin-top: auto; - button{ - display: block; - width: 100%; + border: 1px solid #646464; + border-radius: 2px; + padding: 0 10px; + transition: all 0.15s ease-in-out; + i { + height: 15px; + display: block; + margin-left: 10px; + background-repeat: no-repeat; + background-position: center; + background-size: cover; + transition: all 0.15s ease-in-out; + &.allocation01 { + background-image: url(../../public/static/images/canvas/allocation_icon01_white.svg); + width: 15px; + } + &.allocation02 { + background-image: url(../../public/static/images/canvas/allocation_icon02_white.svg); + width: 18px; + } } + &.act, + &:hover { + color: #101010; + border: 1px solid #101010; + background-color: #fff; + i { + &.allocation01 { + background-image: url(../../public/static/images/canvas/allocation_icon01_black.svg); + } + &.allocation02 { + background-image: url(../../public/static/images/canvas/allocation_icon02_black.svg); + } + } + } + } } -// 오브젝트 배치 -.mb-box{ - margin-bottom: 24px; +// 경사설정 +.slope-wrap { + padding-bottom: 24px; + border-bottom: 1px solid #424242; } -.object-direction-wrap{ - display: flex; - align-items: center; - justify-content: center; -} -.discrimination-tit{ - font-size: 13px; - color: #fff; - font-weight: 500; +// 면형상 배치 +.plane-frame-wrap { + display: flex; + gap: 10px; + .plane-shape-wrap { + flex: none; + width: 73px; + } } -.object-size-wrap{ - display: flex; - min-height: 206px; - gap: 24px; - margin-top: 14px; - .object-size-img{ - position: relative; - flex: none; - width: 200px; - background-color: #fff; - img{ +.plane-shape-menu { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 5px; + .shape-menu-box { + border-radius: 2px; + width: 34px; + height: 34px; + background-color: #373737; + border: 1px solid #676767; + transition: + background 0.15s ease-in-out, + border 0.15s ease-in-out; + .shape-box { + display: flex; + justify-content: center; + align-items: center; + position: relative; + width: 100%; + height: 100%; + border-radius: 2px; + } + &.act, + &:hover { + border-color: #008bff; + background-color: #008bff; + } + } +} + +.shape-library { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 5px; + padding-top: 5px; + .library-btn { + width: 100%; + height: 34px; + border: 1px solid #6c6c6c; + border-radius: 2px; + background-color: #373737; + background-repeat: no-repeat; + background-position: center; + transition: all 0.15s ease-in-out; + &.ico01 { + background-image: url(../../public/static/images/canvas/shape_labrary01.svg); + background-size: 19px 18px; + } + &.ico02 { + background-image: url(../../public/static/images/canvas/shape_labrary02.svg); + background-size: 15px 20px; + } + &.ico03 { + background-image: url(../../public/static/images/canvas/shape_labrary03.svg); + background-size: 19px 16px; + } + &:hover { + border-color: #1083e3; + background-color: #1083e3; + } + } +} + +.plane-detail-wrap { + display: flex; + flex-direction: column; + flex: 1; +} +.plane-shape-wrapper { + flex: 1; + display: flex; + flex-direction: column; + gap: 10px; + .plane-box { + width: 100%; + padding: 10px 18px; + border-radius: 2px; + background-color: #313131; + border: 1px solid #484848; + .plane-box-tit { + font-size: $pop-normal-size; + font-weight: 600; + color: $pop-color; + margin-bottom: 10px; + } + &.shape-box { + .shape-box-inner { + display: flex; + gap: 15px; + min-height: 140px; + .shape-img { + position: relative; + flex: none; + width: 150px; + background-color: #fff; + border-radius: 2px; + img { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); + } } - } -} - -// 표시변경 -.display-change-wrap{ - margin: 24px 0; -} -.warning{ - font-size: $pop-normal-size; - font-weight: $pop-normal-weight; - color: #FFAFAF; -} - -// 각 변 속성 변경 -.radio-grid-wrap{ - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 24px 15px; -} - -// 면 흐름 설정 -.drawing-flow-wrap{ - display: flex; - gap: 10px; - .discrimination-box{ - flex: 1; - display: flex; - flex-direction: column; - .object-direction-wrap{ - flex: 1; - } - &:first-child{ - flex: none; - width: 195px; - } - } -} - -.compas-box{ - display: flex; - align-items: center; - justify-content: center; -} -.compas-box-inner { - position: relative; - width: 200px; - height: 200px; - border-radius: 50%; - - .circle { - position: absolute; - width: 12px; - height: 12px; - border: 1px solid #fff; - border-radius: 50%; - top: 95%; - left: 50%; - transform-origin: 0 -90px; /* 중심에서 반지름 거리만큼 떨어져 위치 */ - cursor:pointer; - z-index: 3; - /* 0번을 180도 위치(아래)에, 13번을 0도 위치(위)에 배치 */ - i{ - position: absolute; - top: 12.5px; - left: 50%; - font-size: 11px; - color: #8B8B8B; - font-weight: 500; - -webkit-user-select: none; - -moz-user-select: none; - -ms-use-select: none; - user-select: none; - } - &:nth-child(1) { transform: rotate(180deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(180deg);}} - &:nth-child(2) { transform: rotate(195deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(165deg);}} - &:nth-child(3) { transform: rotate(210deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(150deg);}} - &:nth-child(4) { transform: rotate(225deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(135deg);}} - &:nth-child(5) { transform: rotate(240deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(120deg);}} - &:nth-child(6) { transform: rotate(255deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(105deg);}} - &:nth-child(7) { transform: rotate(270deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(90deg);}} - &:nth-child(8) { transform: rotate(285deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(75deg);}} - &:nth-child(9) { transform: rotate(300deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(60deg);}} - &:nth-child(10) { transform: rotate(315deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(45deg);}} - &:nth-child(11) { transform: rotate(330deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(30deg);}} - &:nth-child(12) { transform: rotate(345deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(15deg);}} - &:nth-child(13) { transform: rotate(0deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(0deg);}} - &:nth-child(14) { transform: rotate(15deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-15deg);}} - &:nth-child(15) { transform: rotate(30deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-30deg);}} - &:nth-child(16) { transform: rotate(45deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-45deg);}} - &:nth-child(17) { transform: rotate(60deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-60deg);}} - &:nth-child(18) { transform: rotate(75deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-75deg);}} - &:nth-child(19) { transform: rotate(90deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-90deg);}} - &:nth-child(20) { transform: rotate(105deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-105deg);}} - &:nth-child(21) { transform: rotate(120deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-120deg);}} - &:nth-child(22) { transform: rotate(135deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-135deg);}} - &:nth-child(23) { transform: rotate(150deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-150deg);}} - &:nth-child(24) { transform: rotate(165deg) translate(-50%, -50%); i{transform: translateX(-50%) rotate(-165deg);}} - &.act{ - &::after{ - content: ''; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 5px; - height: 5px; - background-color: #fff; - border-radius: 50%; - } - i{ - color: #fff; - } - } - } - .compas{ - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 148px; - height: 148px; - border: 4px solid #fff; - border-radius: 50%; - .compas-arr{ - width: 100%; - height: 100%; - background: url(../../public/static/images/canvas/compas.svg)no-repeat center; - background-size: 122px 122px; - } - } -} - -.draw-flow-wrap{ - margin: 10px 0; -} - -// 지붕모듈선택 -.roof-module-tab{ - display: flex; - align-items: center; - gap: 10px; - margin-bottom: 14px; - .module-tab-bx{ - flex: 1; - height: 34px; - line-height: 31px; - border: 1px solid #484848; - border-radius: 2px; - background-color: transparent; - font-size: 12px; - color: #AAA; - text-align: center; - cursor: default; - transition: all .15s ease-in-out; - &.act{ - background-color: #1083E3; - border: 1px solid #1083E3; - color: #fff; - font-weight: 500; - } - } - .tab-arr{ - display: block; - width: 9px; - height: 14px; - background-repeat: no-repeat; - background-position: center; - background-size: cover; - background-image: url(../../public/static/images/canvas/module_tab_arr.svg); - transition: all .15s ease-in-out; - &.act{ - background-image: url(../../public/static/images/canvas/module_tab_arr_white.svg); - } - } -} - -.roof-module-compas{ - margin-bottom: 24px; - .compas-box-inner{ - width: 280px; - height: 253px; - .circle{ - top: 86%; - // &:nth-child(1), - // &:nth-child(7), - // &:nth-child(13), - // &:nth-child(19){ - // &::before{ - // content: ''; - // position: absolute; - // top: 20px; - // left: 50%; - // transform: translateX(-50%); - // width: 1px; - // height: 6px; - // background-color: #8B8B8B; - // } - // } - i{ - top: 22px; - } - &.act{ - i{color: #8B8B8B;} - } - } - } -} -.center-wrap{ - display: flex; - flex-direction: column; - align-items: center; - gap: 20px; -} - -.module-table-flex-wrap{ - display: flex; - gap: 10px; - .outline-form{ - flex: 1; - } -} - -.module-box-tab{ - display: flex; - .module-btn{ - flex: 1; - border-top: 1px solid #505050; - border-bottom: 1px solid #505050; - border-right: 1px solid #505050; - background-color: #454545; - color: #fff; - height: 30px; - font-size: 12px; - font-weight: 400; - transition: all .15s ease-in-out; - &:first-child{ - border-left: 1px solid #505050; - } - &.act{ - border-color: #fff; - background-color: #fff; - color: #101010; - } - } -} - -.module-table-box{ - flex: 1; - background-color: #3D3D3D; - border-radius: 2px; - .module-table-inner{ - padding: 10px; - .outline-form{ - span{ - width: auto; - } - } - .module-table-tit{ - padding: 10px 0; - font-size: 12px; - color: #fff; - border-bottom: 1px solid #4D4D4D; - } - .eaves-keraba-table{ - width: 100%; - margin-top: 15px; - .eaves-keraba-th{ - width: 72px; - } - .eaves-keraba-th, - .eaves-keraba-td{ - padding-bottom: 5px; - } - } - .self-table-tit{ - font-size: 12px; - font-weight: 500; - color: #fff; - padding-bottom: 15px; - } - } - .warning-guide{ - padding: 20px; - .warning{ - color: #FFCACA; - max-height: 55px; - overflow-y: auto; - padding-right: 30px; - &::-webkit-scrollbar { - width: 4px; - background-color: transparent; - } - &::-webkit-scrollbar-thumb { - background-color: #D9D9D9; - } - &::-webkit-scrollbar-track { - background-color: transparent; - } - } - } -} - -.module-self-table{ - display: table; - border-top: 1px solid #4D4D4D; - border-collapse: collapse; - width: 100%; - .self-table-item{ - display: table-row; - .self-item-td, - .self-item-th{ - display: table-cell; - vertical-align: middle; - border-bottom: 1px solid #4D4D4D; - } - .self-item-th{ - width: 60px; - font-size: 12px; - font-weight: 500; - color: #fff; - } - .self-item-td{ - font-size: 12px; - font-weight: 400; - color: #fff; - padding: 15px 20px; - } - } -} - -.self-table-flx{ - display: flex; - align-items: center; - margin-top: 15px; - button{ - margin-left: auto; - } -} -.hexagonal-wrap{ - .hexagonal-item{ - padding: 15px 0; - border-bottom: 1px solid #4D4D4D; - &:first-child{ - padding-top: 0; - } - &:last-child{ - padding-bottom: 0; - border: none; - } - .hexagonal-flx-auto{ - display: flex; - justify-content: space-between; - } - .hexagonal-flx{ - display: flex; - align-items: center; - button{ - margin-left: auto; - } - } - } -} - -// 회로 및 가대설정 -.circuit-check-inner{ - padding: 5px 0; -} - -.x-scroll-table{ - overflow-x: auto; - padding-bottom: 5px; - .roof-module-table{ - min-width: 1200px; - } - &::-webkit-scrollbar { - height: 4px; - background-color: transparent; - } - &::-webkit-scrollbar-thumb { - background-color: #D9D9D9; - } - &::-webkit-scrollbar-track { - background-color: transparent; - } -} - -.circuit-right-wrap{ - display: flex; - justify-content: flex-end; -} - -.circuit-data-form{ - display: flex; - flex-direction: column; - gap: 5px; - min-height: 60px; - padding: 12px; - border: 1px solid rgba(255, 255, 255, 0.20); - span{ - display: inline-flex; - align-items: center; - .del{ - display: block; - margin-left: 10px; - width: 15px; - height: 15px; - background: url(../../public/static/images/canvas/circuit_del.svg)no-repeat center; - background-size: cover; - } - } -} -.circuit-table-tit{ - color: #fff; - font-size: 12px; - font-weight: 600; - padding: 11px 10px; - background-color: #474747; - border: 1px solid #505050; - border-bottom: none; -} - -.circuit-overflow{ - max-height: 400px; - overflow-y: auto; - margin-bottom: 15px; - &::-webkit-scrollbar { - width: 4px; - background-color: transparent; - } - &::-webkit-scrollbar-thumb { - background-color: #D9D9D9; - } - &::-webkit-scrollbar-track { - background-color: transparent; - } -} - -.circuit-table-flx-wrap{ - display: flex; - gap: 10px; - margin-bottom: 10px; - .circuit-table-flx-box{ - flex: 1; - display: flex; - flex-direction: column; - .bottom-wrap{ - margin-top: auto; - } - .roof-module-table{ - table{ - table-layout: fixed; - } - } - } -} - -.circuit-count-input{ - display: flex; - align-items: center; - gap: 10px; -} - -// 모듈부가기능 -.additional-radio-wrap{ - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 15px 0; - margin-bottom: 24px; -} -.additional-wrap{ - padding: 24px 0; - border-top: 1px solid #424242; -} - -.additional-color-wrap{ - display: flex; - align-items: center; - padding: 5px 0; - gap: 15px; - .additional-color-box{ - display: flex; - align-items: center; - gap: 8px; - .additional-color{ - display: block; - width: 16px; - height: 16px; - &.pink{ - border: 2px solid #ce1c9c; - background-color: #16417D; - } - &.white{ - border: 2px solid #FFF; - background-color: #001027; - } - } - } -} - -// color setting -.color-setting-wrap{ - padding-bottom: 15px; - border-bottom: 1px solid #424242; - .color-tit{ - font-size: 13px; - font-weight: 500; - color: #ffffff; - margin-bottom: 10px; - } - .color-picker{ - .react-colorful{ - width: 100%; - height: auto; - gap: 20px; - .react-colorful__pointer{ - width: 15px; - height: 15px; - border: 4px solid #Fff; - } - .react-colorful__saturation{ - border-radius: 2px; - height: 200px; - border-bottom: 5px solid #000; - } - .react-colorful__last-control{ - border-radius: 2px; - height: 10px; - } - } - .hex-color-box{ - display: flex; - align-items: center; - margin-top: 15px; - .color-box-tit{ - font-size: 12px; - color: #fff; - font-weight: 500; - margin-right: 10px; - } - .color-hex-input{ - width: 150px; - margin-right: 5px; - input{ - width: 100%; - } - } - .color-box{ - display: block; - width: 30px; - height: 30px; - border-radius: 4px; - } - } - .default-color-wrap{ - margin-top: 25px; - .default-tit{ - font-size: 12px; - font-weight: 500; - color: #fff; - margin-bottom: 10px; - } - .color-button-wrap{ - display: grid; - grid-template-columns: repeat(8, 1fr); - gap: 21px; - .default-color{ - display: block; - width: 100%; - height: 30px; - border-radius: 4px; + .shape-data { + flex: 1; + .eaves-keraba-table { + .eaves-keraba-item { + .eaves-keraba-th, + .eaves-keraba-td { + padding-bottom: 10px; + } + &:last-child { + .eaves-keraba-th, + .eaves-keraba-td { + padding-bottom: 0px; } + } } + } } + } } -} - -// 글꼴 설정 팝업 -.font-option-warp{ - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 15px 5px; - margin-bottom: 15px; - .font-option-item{ - .option-item-tit{ - font-size: 12px; - font-weight: 500; - color: #fff; - margin-bottom: 10px; - } - } -} -.font-ex-wrap{ - margin-bottom: 15px; - .font-ex-tit{ - font-size: 12px; - font-weight: 500; - color: #fff; - margin-bottom: 10px; - } - .font-ex-box{ + &.direction-box { + flex: 1; + display: flex; + flex-direction: column; + .plane-direction-box { + flex: 1; display: flex; align-items: center; justify-content: center; width: 100%; - min-height: 80px; - background-color: #fff; + } } - + } } - -// 치수선 설정 -.font-btn-wrap{ - margin-bottom: 15px; - button{ - width: 100%; - height: 30px; - line-height: 28px; +.plane-direction { + width: 150px; + position: relative; + height: 120px; + span { + position: absolute; + font-size: 12px; + font-weight: 500; + color: #b1b1b1; + &.top { + top: 0; + left: 50%; + transform: translateX(-50%); } -} - -.line-color-wrap{ - margin-bottom: 15px; - .color-btn{ - display: block; - width: 100%; - height: 30px; - border-radius: 2px; + &.right { + top: 50%; + right: 0; + transform: translateY(-50%); } -} - -.form-box{ - width: 100%; - background-color: #fff; - padding: 10px 15px 20px; - .line-form{ - position: relative; - display: flex; - flex-direction: column; - justify-content: flex-end; - min-width: 102px; - min-height: 40px; - margin: 0 auto; - - &::before{ - content: ''; - position: absolute; - bottom: 0px; - left: 0; - width: 100%; - height: 40px; - border-left: 1px dashed #101010; - border-right: 1px dashed #101010; - } - .line-font-box{ - .font{ - display: block; - padding-bottom: 15px; - color: #101010; - text-align: center; - line-height: 1; - } - .line{ - position: relative; - display: block; - width: 100%; - height: 1px; - border-radius: 30px; - &::before{ - content: ''; - position: absolute; - top: 50%; - transform: translateY(-50%) rotate(45deg); - left: 1px; - width: 9px; - height:+ 9px; - border: 1px solid; - border-color: inherit; - border-top: none; - border-right: none; - } - &::after{ - content: ''; - position: absolute; - top: 50%; - transform: translateY(-50%) rotate(45deg); - right: 1px; - width: 9px; - height: 9px; - border: 1px solid; - border-color: inherit; - border-bottom: none; - border-left: none; - } - } - } + &.bottom { + bottom: 0; + left: 50%; + transform: translateX(-50%); } + &.left { + top: 50%; + left: 0; + transform: translateY(-50%); + } + } + .plane-btn { + position: absolute; + width: 28px; + height: 28px; + background-color: #777777; + background-image: url(../../public/static/images/canvas/plane_arr.svg); + background-size: 12px 13px; + background-repeat: no-repeat; + background-position: center; + border-radius: 50%; + transition: all 0.15s ease-in-out; + &.up { + top: 22px; + left: 50%; + transform: translateX(-50%); + } + &.right { + top: 50%; + right: 32px; + transform: translateY(-50%) rotate(90deg); + } + &.down { + bottom: 22px; + left: 50%; + transform: translateX(-50%) rotate(180deg); + } + &.left { + top: 50%; + left: 32px; + transform: translateY(-50%) rotate(270deg); + } + &:hover, + &.act { + background-color: #fff; + background-image: url(../../public/static/images/canvas/plane_arr_act.svg); + } + } } -// 사이즈 변경 -.size-inner-warp{ - position: relative; +.plane-tab-guide { + font-size: $pop-normal-size; + font-weight: $pop-normal-weight; + color: $pop-color; + margin-top: 10px; } -.size-check-wrap{ - position: relative; +.plane-shape-btn { + padding-top: 10px; + margin-top: auto; + button { display: block; - width: 132px; - height: 132px; - margin: 0 auto; - .size-btn{ - position: absolute; - width: 16px; - height: 16px; - border: 1px solid #fff; - border-radius: 50%; - &.act{ - &::after{ - content: ''; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 8px; - height: 8px; - background-color: #fff; - border-radius: 50%; - } - } - &:nth-child(1){ top: 0; left: 0; } - &:nth-child(2){ top: 0; right: 0; } - &:nth-child(3){ bottom: 0; left: 0; } - &:nth-child(4){ bottom: 0; right: 0; } + width: 100%; + } +} + +// 오브젝트 배치 +.mb-box { + margin-bottom: 24px; +} + +.object-direction-wrap { + display: flex; + align-items: center; + justify-content: center; +} +.discrimination-tit { + font-size: 13px; + color: #fff; + font-weight: 500; +} + +.object-size-wrap { + display: flex; + min-height: 206px; + gap: 24px; + margin-top: 14px; + .object-size-img { + position: relative; + flex: none; + width: 200px; + background-color: #fff; + img { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); } - .size-box{ + } +} + +// 표시변경 +.display-change-wrap { + margin: 24px 0; +} +.warning { + font-size: $pop-normal-size; + font-weight: $pop-normal-weight; + color: #ffafaf; +} + +// 각 변 속성 변경 +.radio-grid-wrap { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px 15px; +} + +// 면 흐름 설정 +.drawing-flow-wrap { + display: flex; + gap: 10px; + .discrimination-box { + flex: 1; + display: flex; + flex-direction: column; + .object-direction-wrap { + flex: 1; + } + &:first-child { + flex: none; + width: 195px; + } + } +} + +.compas-box { + display: flex; + align-items: center; + justify-content: center; +} +.compas-box-inner { + position: relative; + width: 200px; + height: 200px; + border-radius: 50%; + + .circle { + position: absolute; + width: 12px; + height: 12px; + border: 1px solid #fff; + border-radius: 50%; + top: 95%; + left: 50%; + transform-origin: 0 -90px; /* 중심에서 반지름 거리만큼 떨어져 위치 */ + cursor: pointer; + z-index: 3; + /* 0번을 180도 위치(아래)에, 13번을 0도 위치(위)에 배치 */ + i { + position: absolute; + top: 12.5px; + left: 50%; + font-size: 11px; + color: #8b8b8b; + font-weight: 500; + -webkit-user-select: none; + -moz-user-select: none; + -ms-use-select: none; + user-select: none; + } + &:nth-child(1) { + transform: rotate(180deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(180deg); + } + } + &:nth-child(2) { + transform: rotate(195deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(165deg); + } + } + &:nth-child(3) { + transform: rotate(210deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(150deg); + } + } + &:nth-child(4) { + transform: rotate(225deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(135deg); + } + } + &:nth-child(5) { + transform: rotate(240deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(120deg); + } + } + &:nth-child(6) { + transform: rotate(255deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(105deg); + } + } + &:nth-child(7) { + transform: rotate(270deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(90deg); + } + } + &:nth-child(8) { + transform: rotate(285deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(75deg); + } + } + &:nth-child(9) { + transform: rotate(300deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(60deg); + } + } + &:nth-child(10) { + transform: rotate(315deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(45deg); + } + } + &:nth-child(11) { + transform: rotate(330deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(30deg); + } + } + &:nth-child(12) { + transform: rotate(345deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(15deg); + } + } + &:nth-child(13) { + transform: rotate(0deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(0deg); + } + } + &:nth-child(14) { + transform: rotate(15deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(-15deg); + } + } + &:nth-child(15) { + transform: rotate(30deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(-30deg); + } + } + &:nth-child(16) { + transform: rotate(45deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(-45deg); + } + } + &:nth-child(17) { + transform: rotate(60deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(-60deg); + } + } + &:nth-child(18) { + transform: rotate(75deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(-75deg); + } + } + &:nth-child(19) { + transform: rotate(90deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(-90deg); + } + } + &:nth-child(20) { + transform: rotate(105deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(-105deg); + } + } + &:nth-child(21) { + transform: rotate(120deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(-120deg); + } + } + &:nth-child(22) { + transform: rotate(135deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(-135deg); + } + } + &:nth-child(23) { + transform: rotate(150deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(-150deg); + } + } + &:nth-child(24) { + transform: rotate(165deg) translate(-50%, -50%); + i { + transform: translateX(-50%) rotate(-165deg); + } + } + &.act { + &::after { + content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); - width: 100px; - height: 100px; + width: 5px; + height: 5px; background-color: #fff; + border-radius: 50%; + } + i { + color: #fff; + } } -} - -.size-option-top{ - margin-bottom: 15px; -} -.size-option-side{ + } + .compas { position: absolute; top: 50%; - left: 0; - transform: translateY(-50%); -} -.size-option-wrap{ - width: 88px; - margin: 0 auto; - .size-option{ - display: flex; - align-items: center; - input{ - width: 100%; - flex: 1; - } - span{ - flex: none; - } + left: 50%; + transform: translate(-50%, -50%); + width: 148px; + height: 148px; + border: 4px solid #fff; + border-radius: 50%; + .compas-arr { + width: 100%; + height: 100%; + background: url(../../public/static/images/canvas/compas.svg) no-repeat center; + background-size: 122px 122px; } + } +} + +.draw-flow-wrap { + margin: 10px 0; +} + +// 지붕모듈선택 +.roof-module-tab { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 14px; + .module-tab-bx { + flex: 1; + height: 34px; + line-height: 31px; + border: 1px solid #484848; + border-radius: 2px; + background-color: transparent; + font-size: 12px; + color: #aaa; + text-align: center; + cursor: default; + transition: all 0.15s ease-in-out; + &.act { + background-color: #1083e3; + border: 1px solid #1083e3; + color: #fff; + font-weight: 500; + } + } + .tab-arr { + display: block; + width: 9px; + height: 14px; + background-repeat: no-repeat; + background-position: center; + background-size: cover; + background-image: url(../../public/static/images/canvas/module_tab_arr.svg); + transition: all 0.15s ease-in-out; + &.act { + background-image: url(../../public/static/images/canvas/module_tab_arr_white.svg); + } + } +} + +.roof-module-compas { + margin-bottom: 24px; + .compas-box-inner { + width: 280px; + height: 253px; + .circle { + top: 86%; + // &:nth-child(1), + // &:nth-child(7), + // &:nth-child(13), + // &:nth-child(19){ + // &::before{ + // content: ''; + // position: absolute; + // top: 20px; + // left: 50%; + // transform: translateX(-50%); + // width: 1px; + // height: 6px; + // background-color: #8B8B8B; + // } + // } + i { + top: 22px; + } + &.act { + i { + color: #8b8b8b; + } + } + } + } +} +.center-wrap { + display: flex; + flex-direction: column; + align-items: center; + gap: 20px; +} + +.module-table-flex-wrap { + display: flex; + gap: 10px; + .outline-form { + flex: 1; + } +} + +.module-box-tab { + display: flex; + .module-btn { + flex: 1; + border-top: 1px solid #505050; + border-bottom: 1px solid #505050; + border-right: 1px solid #505050; + background-color: #454545; + color: #fff; + height: 30px; + font-size: 12px; + font-weight: 400; + transition: all 0.15s ease-in-out; + &:first-child { + border-left: 1px solid #505050; + } + &.act { + border-color: #fff; + background-color: #fff; + color: #101010; + } + } +} + +.module-table-box { + flex: 1; + background-color: #3d3d3d; + border-radius: 2px; + .module-table-inner { + padding: 10px; + .outline-form { + span { + width: auto; + } + } + .module-table-tit { + padding: 10px 0; + font-size: 12px; + color: #fff; + border-bottom: 1px solid #4d4d4d; + } + .eaves-keraba-table { + width: 100%; + margin-top: 15px; + .eaves-keraba-th { + width: 72px; + } + .eaves-keraba-th, + .eaves-keraba-td { + padding-bottom: 5px; + } + } + .self-table-tit { + font-size: 12px; + font-weight: 500; + color: #fff; + padding-bottom: 15px; + } + } + .warning-guide { + padding: 20px; + .warning { + color: #ffcaca; + max-height: 55px; + overflow-y: auto; + padding-right: 30px; + &::-webkit-scrollbar { + width: 4px; + background-color: transparent; + } + &::-webkit-scrollbar-thumb { + background-color: #d9d9d9; + } + &::-webkit-scrollbar-track { + background-color: transparent; + } + } + } +} + +.module-self-table { + display: table; + border-top: 1px solid #4d4d4d; + border-collapse: collapse; + width: 100%; + .self-table-item { + display: table-row; + .self-item-td, + .self-item-th { + display: table-cell; + vertical-align: middle; + border-bottom: 1px solid #4d4d4d; + } + .self-item-th { + width: 60px; + font-size: 12px; + font-weight: 500; + color: #fff; + } + .self-item-td { + font-size: 12px; + font-weight: 400; + color: #fff; + padding: 15px 20px; + } + } +} + +.self-table-flx { + display: flex; + align-items: center; + margin-top: 15px; + button { + margin-left: auto; + } +} +.hexagonal-wrap { + .hexagonal-item { + padding: 15px 0; + border-bottom: 1px solid #4d4d4d; + &:first-child { + padding-top: 0; + } + &:last-child { + padding-bottom: 0; + border: none; + } + .hexagonal-flx-auto { + display: flex; + justify-content: space-between; + } + .hexagonal-flx { + display: flex; + align-items: center; + button { + margin-left: auto; + } + } + } +} + +// 회로 및 가대설정 +.circuit-check-inner { + padding: 5px 0; +} + +.x-scroll-table { + overflow-x: auto; + padding-bottom: 5px; + .roof-module-table { + min-width: 1200px; + } + &::-webkit-scrollbar { + height: 4px; + background-color: transparent; + } + &::-webkit-scrollbar-thumb { + background-color: #d9d9d9; + } + &::-webkit-scrollbar-track { + background-color: transparent; + } +} + +.circuit-right-wrap { + display: flex; + justify-content: flex-end; +} + +.circuit-data-form { + display: flex; + flex-direction: column; + gap: 5px; + min-height: 60px; + padding: 12px; + border: 1px solid rgba(255, 255, 255, 0.2); + span { + display: inline-flex; + align-items: center; + .del { + display: block; + margin-left: 10px; + width: 15px; + height: 15px; + background: url(../../public/static/images/canvas/circuit_del.svg) no-repeat center; + background-size: cover; + } + } +} +.circuit-table-tit { + color: #fff; + font-size: 12px; + font-weight: 600; + padding: 11px 10px; + background-color: #474747; + border: 1px solid #505050; + border-bottom: none; +} + +.circuit-overflow { + max-height: 400px; + overflow-y: auto; + margin-bottom: 15px; + &::-webkit-scrollbar { + width: 4px; + background-color: transparent; + } + &::-webkit-scrollbar-thumb { + background-color: #d9d9d9; + } + &::-webkit-scrollbar-track { + background-color: transparent; + } +} + +.circuit-table-flx-wrap { + display: flex; + gap: 10px; + margin-bottom: 10px; + .circuit-table-flx-box { + flex: 1; + display: flex; + flex-direction: column; + .bottom-wrap { + margin-top: auto; + } + .roof-module-table { + table { + table-layout: fixed; + } + } + } +} + +.circuit-count-input { + display: flex; + align-items: center; + gap: 10px; +} + +// 모듈부가기능 +.additional-radio-wrap { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 15px 0; + margin-bottom: 24px; +} +.additional-wrap { + padding: 24px 0; + border-top: 1px solid #424242; +} + +.additional-color-wrap { + display: flex; + align-items: center; + padding: 5px 0; + gap: 15px; + .additional-color-box { + display: flex; + align-items: center; + gap: 8px; + .additional-color { + display: block; + width: 16px; + height: 16px; + &.pink { + border: 2px solid #ce1c9c; + background-color: #16417d; + } + &.white { + border: 2px solid #fff; + background-color: #001027; + } + } + } +} + +// color setting +.color-setting-wrap { + padding-bottom: 15px; + border-bottom: 1px solid #424242; + .color-tit { + font-size: 13px; + font-weight: 500; + color: #ffffff; + margin-bottom: 10px; + } + .color-picker { + .react-colorful { + width: 100%; + height: auto; + gap: 20px; + .react-colorful__pointer { + width: 15px; + height: 15px; + border: 4px solid #fff; + } + .react-colorful__saturation { + border-radius: 2px; + height: 200px; + border-bottom: 5px solid #000; + } + .react-colorful__last-control { + border-radius: 2px; + height: 10px; + } + } + .hex-color-box { + display: flex; + align-items: center; + margin-top: 15px; + .color-box-tit { + font-size: 12px; + color: #fff; + font-weight: 500; + margin-right: 10px; + } + .color-hex-input { + width: 150px; + margin-right: 5px; + input { + width: 100%; + } + } + .color-box { + display: block; + width: 30px; + height: 30px; + border-radius: 4px; + } + } + .default-color-wrap { + margin-top: 25px; + .default-tit { + font-size: 12px; + font-weight: 500; + color: #fff; + margin-bottom: 10px; + } + .color-button-wrap { + display: grid; + grid-template-columns: repeat(8, 1fr); + gap: 21px; + .default-color { + display: block; + width: 100%; + height: 30px; + border-radius: 4px; + } + } + } + } +} + +// 글꼴 설정 팝업 +.font-option-warp { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 15px 5px; + margin-bottom: 15px; + .font-option-item { + .option-item-tit { + font-size: 12px; + font-weight: 500; + color: #fff; + margin-bottom: 10px; + } + } +} +.font-ex-wrap { + margin-bottom: 15px; + .font-ex-tit { + font-size: 12px; + font-weight: 500; + color: #fff; + margin-bottom: 10px; + } + .font-ex-box { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + min-height: 80px; + background-color: #fff; + } +} + +// 치수선 설정 +.font-btn-wrap { + margin-bottom: 15px; + button { + width: 100%; + height: 30px; + line-height: 28px; + } +} + +.line-color-wrap { + margin-bottom: 15px; + .color-btn { + display: block; + width: 100%; + height: 30px; + border-radius: 2px; + } +} + +.form-box { + width: 100%; + background-color: #fff; + padding: 10px 15px 20px; + .line-form { + position: relative; + display: flex; + flex-direction: column; + justify-content: flex-end; + min-width: 102px; + min-height: 40px; + margin: 0 auto; + + &::before { + content: ''; + position: absolute; + bottom: 0px; + left: 0; + width: 100%; + height: 40px; + border-left: 1px dashed #101010; + border-right: 1px dashed #101010; + } + .line-font-box { + .font { + display: block; + padding-bottom: 15px; + color: #101010; + text-align: center; + line-height: 1; + } + .line { + position: relative; + display: block; + width: 100%; + height: 1px; + border-radius: 30px; + &::before { + content: ''; + position: absolute; + top: 50%; + transform: translateY(-50%) rotate(45deg); + left: 1px; + width: 9px; + height: +9px; + border: 1px solid; + border-color: inherit; + border-top: none; + border-right: none; + } + &::after { + content: ''; + position: absolute; + top: 50%; + transform: translateY(-50%) rotate(45deg); + right: 1px; + width: 9px; + height: 9px; + border: 1px solid; + border-color: inherit; + border-bottom: none; + border-left: none; + } + } + } + } +} + +// 사이즈 변경 +.size-inner-warp { + position: relative; +} +.size-check-wrap { + position: relative; + display: block; + width: 132px; + height: 132px; + margin: 0 auto; + .size-btn { + position: absolute; + width: 16px; + height: 16px; + border: 1px solid #fff; + border-radius: 50%; + &.act { + &::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 8px; + height: 8px; + background-color: #fff; + border-radius: 50%; + } + } + &:nth-child(1) { + top: 0; + left: 0; + } + &:nth-child(2) { + top: 0; + right: 0; + } + &:nth-child(3) { + bottom: 0; + left: 0; + } + &:nth-child(4) { + bottom: 0; + right: 0; + } + } + .size-box { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 100px; + height: 100px; + background-color: #fff; + } +} + +.size-option-top { + margin-bottom: 15px; +} +.size-option-side { + position: absolute; + top: 50%; + left: 0; + transform: translateY(-50%); +} +.size-option-wrap { + width: 88px; + margin: 0 auto; + .size-option { + display: flex; + align-items: center; + input { + width: 100%; + flex: 1; + } + span { + flex: none; + } + } } //이미지 크기 설정 -.range-wrap{ - display: flex; - align-items: center; - input{ - flex: 1; - margin-right: 10px; - } - label{ - flex: none; - text-align: right; - width: 38px; - font-size: 13px; - color: #fff; - font-weight: 500; - } +.range-wrap { + display: flex; + align-items: center; + input { + flex: 1; + margin-right: 10px; + } + label { + flex: none; + text-align: right; + width: 38px; + font-size: 13px; + color: #fff; + font-weight: 500; + } } // 이미지 불러오기 -.img-flex-box{ - display: flex; - align-items: center; +.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; - } +.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; + } + border-bottom: 1px solid #424242; } // 지붕모듈선택 변경 -.module-table-box{ - &.none-flex{ - flex: none; - width: 230px; - } +.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; +.module-table-flex-wrap { + &.tab2 { margin-top: 10px; -} \ No newline at end of file + 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; +} diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index b27f91db..991facc0 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -751,16 +751,8 @@ export const pointsToTurfPolygon = (points) => { return turf.polygon([coordinates]) } -export const polygonToTurfPolygon = (polygon) => { - const coordinates = polygon.points.map((point) => [point.x, point.y]) - coordinates.push(coordinates[0]) - return turf.polygon( - [coordinates], - {}, - { - parentId: polygon.parentId, - }, - ) +export function isOverlap(polygon1, polygon2) { + return turf.booleanOverlap(polygon1, polygon2) } export const triangleToPolygon = (triangle) => { @@ -792,7 +784,7 @@ export const rectToPolygon = (rect) => { } //면형상 선택 클릭시 지붕 패턴 입히기 -export function setSurfaceShapePattern(polygon, mode = 'onlyBorder', trestleMode = false) { +function setSurfaceShapePattern(polygon, mode = 'onlyBorder', trestleMode = false) { const ratio = window.devicePixelRatio || 1 let width = 265 / 10 @@ -1003,3 +995,17 @@ export function findAndRemoveClosestPoint(targetPoint, points) { return closestPoint } + +export function polygonToTurfPolygon(object, current = false) { + let coordinates + coordinates = object.points.map((point) => [point.x, point.y]) + if (current) coordinates = object.getCurrentPoints().map((point) => [point.x, point.y]) + coordinates.push(coordinates[0]) + return turf.polygon( + [coordinates], + {}, + { + parentId: object.parentId, + }, + ) +} diff --git a/src/util/common-utils.js b/src/util/common-utils.js index 95e6be87..47498464 100644 --- a/src/util/common-utils.js +++ b/src/util/common-utils.js @@ -94,6 +94,11 @@ export const inputNumberCheck = (e) => { } } +// 값이 숫자인지 확인 +export const numberCheck = (value) => { + return !isNaN(value) +} + /** * 파이프함수 정의 * @param {...any} fns 순수함수들