diff --git a/.env.development b/.env.development index 468efc67..933db5d3 100644 --- a/.env.development +++ b/.env.development @@ -8,4 +8,7 @@ DATABASE_URL="sqlserver://mssql.devgrr.kr:1433;database=qcast;user=qcast;passwor SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y=" -NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_bV5zuYMyyIYFlOb3" \ No newline at end of file +NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_bV5zuYMyyIYFlOb3" + +NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-local.q-cells.jp:8120/eos/login/autoLogin" +NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-local.q-cells.jp:8120/qm/login/autoLogin" \ No newline at end of file diff --git a/.env.production b/.env.production index 2c41bf76..9bad1719 100644 --- a/.env.production +++ b/.env.production @@ -6,4 +6,7 @@ DATABASE_URL="" SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y=" -NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_bV5zuYMyyIYFlOb3" \ No newline at end of file +NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_bV5zuYMyyIYFlOb3" + +NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-local.q-cells.jp:8120/eos/login/autoLogin" +NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-local.q-cells.jp:8120/qm/login/autoLogin" \ No newline at end of file diff --git a/package.json b/package.json index 260bfe8e..4dfef6dd 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "framer-motion": "^11.2.13", "fs": "^0.0.1-security", "iron-session": "^8.0.2", + "js-cookie": "^3.0.5", "mathjs": "^13.0.2", "mssql": "^11.0.1", "next": "14.2.3", @@ -43,7 +44,7 @@ "prettier": "^3.3.3", "prisma": "^5.18.0", "react-color-palette": "^7.2.2", - "react-dropdown-select": "^4.11.3", + "react-select": "^5.8.1", "sass": "^1.77.8", "tailwindcss": "^3.4.1" } diff --git a/public/static/images/canvas/circuit_del.svg b/public/static/images/canvas/circuit_del.svg new file mode 100644 index 00000000..48a3c49d --- /dev/null +++ b/public/static/images/canvas/circuit_del.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/static/images/canvas/object_img01.svg b/public/static/images/canvas/object_img01.svg index a4ebac98..1d72f00b 100644 --- a/public/static/images/canvas/object_img01.svg +++ b/public/static/images/canvas/object_img01.svg @@ -1,5 +1,5 @@ - - + + @@ -9,13 +9,8 @@ - - - - - - + diff --git a/public/static/images/sub/community_clip.svg b/public/static/images/sub/community_clip.svg new file mode 100644 index 00000000..50092a01 --- /dev/null +++ b/public/static/images/sub/community_clip.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/static/images/sub/community_pop_close.svg b/public/static/images/sub/community_pop_close.svg new file mode 100644 index 00000000..e04458f2 --- /dev/null +++ b/public/static/images/sub/community_pop_close.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/static/images/sub/community_search.svg b/public/static/images/sub/community_search.svg new file mode 100644 index 00000000..e83caf34 --- /dev/null +++ b/public/static/images/sub/community_search.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/static/images/sub/file_down_btn.svg b/public/static/images/sub/file_down_btn.svg new file mode 100644 index 00000000..3159305a --- /dev/null +++ b/public/static/images/sub/file_down_btn.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/static/images/sub/grid-btn-excel.svg b/public/static/images/sub/grid-btn-excel.svg new file mode 100644 index 00000000..11f5bebc --- /dev/null +++ b/public/static/images/sub/grid-btn-excel.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/static/images/sub/grid-btn-file.svg b/public/static/images/sub/grid-btn-file.svg new file mode 100644 index 00000000..bf4edf88 --- /dev/null +++ b/public/static/images/sub/grid-btn-file.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/public/static/images/sub/grid_tip.svg b/public/static/images/sub/grid_tip.svg new file mode 100644 index 00000000..d60455dd --- /dev/null +++ b/public/static/images/sub/grid_tip.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/app/SessionProvider.js b/src/app/SessionProvider.js new file mode 100644 index 00000000..d8f3caa9 --- /dev/null +++ b/src/app/SessionProvider.js @@ -0,0 +1,12 @@ +'use client' + +import { createContext, useState } from 'react' + +export const SessionContext = createContext({ + session: {}, +}) + +export default function SessionProvider({ useSession, children }) { + const [session, setSession] = useState(useSession) + return {children} +} diff --git a/src/app/community/archive/page.jsx b/src/app/community/archive/page.jsx index 6917f228..308e02f3 100644 --- a/src/app/community/archive/page.jsx +++ b/src/app/community/archive/page.jsx @@ -7,10 +7,7 @@ export default async function CommunityArchivePage() { return ( <> - -
- -
+ ) } diff --git a/src/app/community/faq/page.jsx b/src/app/community/faq/page.jsx index 2b9d5452..054f9007 100644 --- a/src/app/community/faq/page.jsx +++ b/src/app/community/faq/page.jsx @@ -1,4 +1,3 @@ -import Hero from '@/components/Hero' import Faq from '@/components/community/Faq' import { initCheck } from '@/util/session-util' @@ -7,10 +6,7 @@ export default async function CommunityFaqPage() { return ( <> - -
- -
+ ) } diff --git a/src/app/community/notice/page.jsx b/src/app/community/notice/page.jsx index d2157b20..a3453e64 100644 --- a/src/app/community/notice/page.jsx +++ b/src/app/community/notice/page.jsx @@ -1,4 +1,3 @@ -import Hero from '@/components/Hero' import Notice from '@/components/community/Notice' import { initCheck } from '@/util/session-util' @@ -7,10 +6,7 @@ export default async function CommunityNoticePage() { return ( <> - -
- -
+ ) } diff --git a/src/app/join/complete/page.jsx b/src/app/join/complete/page.jsx index 3f9fc462..3f134e58 100644 --- a/src/app/join/complete/page.jsx +++ b/src/app/join/complete/page.jsx @@ -1,19 +1,9 @@ -'use client' - -import { useMessage } from '@/hooks/useMessage' - -export default function CompletePage() { - const { getMessage } = useMessage() +import JoinComplete from '@/components/auth/JoinComplete' +export default function JoinCompletePage() { return ( <> -
-

{getMessage('join.complete.title')}

-
{getMessage('join.complete.contents')}
-
- {getMessage('join.complete.email_comment')} : {getMessage('join.complete.email')} -
-
+ ) } diff --git a/src/app/layout.js b/src/app/layout.js index f7aafcd0..e59e28d1 100644 --- a/src/app/layout.js +++ b/src/app/layout.js @@ -4,7 +4,6 @@ import { headers } from 'next/headers' import { redirect } from 'next/navigation' import { getSession } from '@/lib/authActions' import RecoilRootWrapper from './RecoilWrapper' -import UIProvider from './UIProvider' import { ToastContainer } from 'react-toastify' @@ -14,7 +13,9 @@ import QModal from '@/components/common/modal/QModal' import './globals.css' import '../styles/style.scss' +import '../styles/contents.scss' import Dimmed from '@/components/ui/Dimmed' +import SessionProvider from './SessionProvider' // const inter = Inter({ subsets: ['latin'] }) @@ -48,6 +49,8 @@ export default async function RootLayout({ children }) { telNo: session.telNo, fax: session.fax, email: session.email, + storeLvl: session.storeLvl, + groupId: session.groupId, pwdInitYn: session.pwdInitYn, isLoggedIn: session.isLoggedIn, } @@ -61,21 +64,26 @@ export default async function RootLayout({ children }) { - {headerPathname !== '/login' ? ( + {headerPathname === '/login' || headerPathname === '/join' ? ( + {children} + ) : (
- -
- - {children} +
+ + + {children} + +
+
+
+ COPYRIGHT©2024 Hanwha Japan All Rights Reserved.
- +
- ) : ( - {children} )} - {/* */} + diff --git a/src/app/login/page.jsx b/src/app/login/page.jsx index 3ff0edd7..0686da2e 100644 --- a/src/app/login/page.jsx +++ b/src/app/login/page.jsx @@ -1,10 +1,9 @@ import Login from '@/components/auth/Login' -import NewLogin from '@/components/auth/NewLogin' export default function LoginPage() { return ( <> - + ) } diff --git a/src/common/common.js b/src/common/common.js index bc067077..1ca3e8ca 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -94,10 +94,21 @@ export const LineType = { // 오브젝트 배치 > 개구배치, 그림자배치 export const BATCH_TYPE = { OPENING: 'opening', + OPENING_TEMP: 'openingTemp', SHADOW: 'shadow', + SHADOW_TEMP: 'shadowTemp', + TRIANGLE_DORMER: 'triangleDormer', + TRIANGLE_DORMER_TEMP: 'triangleDormerTemp', + PENTAGON_DORMER: 'pentagonDormer', + PENTAGON_DORMER_TEMP: 'pentagonDormerTemp', } // 오브젝트 배치 > 프리입력, 치수입력 export const INPUT_TYPE = { FREE: 'free', DIMENSION: 'dimension', } + +export const POLYGON_TYPE = { + ROOF: 'roof', + TRESTLE: 'trestle', +} diff --git a/src/components/Main.jsx b/src/components/Main.jsx index 69c90071..76676cd1 100644 --- a/src/components/Main.jsx +++ b/src/components/Main.jsx @@ -13,6 +13,7 @@ import { stuffSearchState } from '@/store/stuffAtom' import { useForm } from 'react-hook-form' import '@/styles/contents.scss' import ChangePasswordPop from './main/ChangePasswordPop' +import { searchState } from '@/store/boardAtom' export default function MainPage() { const [sessionState, setSessionState] = useRecoilState(sessionStore) @@ -36,6 +37,8 @@ export default function MainPage() { const [stuffSearch, setStuffSearch] = useRecoilState(stuffSearchState) + const [searchForm, setSearchForm] = useRecoilState(searchState) + useEffect(() => { if (sessionState.pwdInitYn === 'Y') { fetchObjectList() @@ -72,14 +75,8 @@ export default function MainPage() { }) router.push('/management/stuff') } else { - alert('작업중') - return - - //FAQ일떄 - //faq리코일에 - //searchValue= e.target.value - //mainFlag:'Y' - // router.push('/community/faq') + setSearchForm({ ...searchForm, searchValue: searchTxt, mainFlag: 'Y' }) + router.push('/community/faq') } } } @@ -100,13 +97,8 @@ export default function MainPage() { router.push('/management/stuff') } else { - alert('작업중') - return - //FAQ일떄 - //faq리코일에 - //searchValue= e.target.value - //mainFlag:'Y' - // router.push('/community/faq') + setSearchForm({ ...searchForm, searchValue: searchTxt, mainFlag: 'Y' }) + router.push('/community/faq') } } diff --git a/src/components/Playground.jsx b/src/components/Playground.jsx index 5d54c858..3bdb7ccb 100644 --- a/src/components/Playground.jsx +++ b/src/components/Playground.jsx @@ -20,6 +20,7 @@ import Image from 'next/image' import QInput from './common/input/Qinput' import QSelect from './common/select/QSelect' +import QPagination from './common/pagination/QPagination' export default function Playground() { const [useCadFile, setUseCadFile] = useRecoilState(useCadFileState) @@ -29,7 +30,7 @@ export default function Playground() { const fileRef = useRef(null) const queryRef = useRef(null) const [zoom, setZoom] = useState(20) - const { get, promisePost } = useAxios() + const { get, promiseGet, promisePost } = useAxios() const testVar = process.env.NEXT_PUBLIC_TEST const converterUrl = process.env.NEXT_PUBLIC_CONVERTER_API_URL const { getMessage } = useMessage() @@ -41,6 +42,9 @@ export default function Playground() { const [radioInput, setRadioInput] = useState('') const [checkboxInput, setCheckboxInput] = useState([]) const [selectedValue, setSelectedValue] = useState('') + + const [users, setUsers] = useState([]) + useEffect(() => { console.log('textInput:', textInput) }, [textInput]) @@ -130,6 +134,20 @@ export default function Playground() { }) } + const paginationProps = { + pageNo: 1, + pageSize: 10, + pagePerBlock: 10, + totalCount: 501, + handleChangePage: (page) => { + console.log('page', page) + }, + } + + useEffect(() => { + console.log('users:', users) + }, [users]) + return ( <>
@@ -290,6 +308,35 @@ export default function Playground() { Sweetalert - confirm
+
+ +
+
+ +
+
+ +
) diff --git a/src/components/Settings.jsx b/src/components/Settings.jsx index 43288dbe..51680711 100644 --- a/src/components/Settings.jsx +++ b/src/components/Settings.jsx @@ -3,7 +3,8 @@ import React, { useEffect, useState } from 'react' import { Button } from '@nextui-org/react' -import { get, post } from '@/lib/Axios' +import { useAxios } from '@/hooks/useAxios' + import { useRecoilState } from 'recoil' import { customSettingsState } from '@/store/canvasAtom' import { modalContent, modalState } from '@/store/modalAtom' @@ -20,6 +21,8 @@ export default function Settings() { const [open, setOpen] = useRecoilState(modalState) const [contents, setContent] = useRecoilState(modalContent) + const { get, post } = useAxios() + const handleSavePopup = () => { console.log('color ', color) } diff --git a/src/components/auth/Join.jsx b/src/components/auth/Join.jsx index ae100e68..3412efdb 100644 --- a/src/components/auth/Join.jsx +++ b/src/components/auth/Join.jsx @@ -1,30 +1,28 @@ 'use client' import { useAxios } from '@/hooks/useAxios' -import { redirect } from 'next/navigation' +import { useRouter } from 'next/navigation' import { useMessage } from '@/hooks/useMessage' +import Cookies from 'js-cookie' export default function Join() { const { getMessage } = useMessage() - const { post } = useAxios() + const { promisePost } = useAxios() + const router = useRouter() + + // 가입 신청 + const joinProcess = async (e) => { + e.preventDefault() + const formData = new FormData(e.target) - const joinProcess = async (formData) => { const param = { - langCd: 'JA', - lastEditUser: formData.get('userId'), storeQcastNm: formData.get('storeQcastNm'), storeQcastNmKana: formData.get('storeQcastNmKana'), postCd: formData.get('postCd'), addr: formData.get('addr'), telNo: formData.get('telNo'), fax: formData.get('fax'), - payTermsCd: 'JB02', - kamId: 'E1101011', - qtCompNm: formData.get('qtCompNm'), - qtPostCd: formData.get('qtPostCd'), - qtAddr: formData.get('qtAddr'), - qtTelNo: formData.get('qtTelNo'), - qtFax: formData.get('qtFax'), + bizNo: formData.get('bizNo'), userInfo: { userId: formData.get('userId'), userNm: formData.get('userNm'), @@ -36,287 +34,268 @@ export default function Join() { }, } - await post({ url: '/api/login/v1.0/user/join', data: param }).then((res) => { - if (res) { - if (res.result.resultCode == 'S') { - redirect('/join/complete') - } else { - alert(res.result.resultMsg) + await promisePost({ url: '/api/login/v1.0/user/join', data: param }) + .then((res) => { + if (res) { + if (res.data.result.resultCode == 'S') { + Cookies.set('joinEmail', formData.get('email'), { expires: 1 }) + router.push('/join/complete') + } else { + alert(res.data.result.resultMsg) + } } - } - }) + }) + .catch((error) => { + alert(error.response.data.message) + }) } return ( -
-

{getMessage('join.title')}

-
-
-
- ● {getMessage('join.sub1.title')} (*{getMessage('common.require')}) {getMessage('join.sub1.comment')} +
+
+ +
{getMessage('join.title')}
+
+
+
+

+ {getMessage('join.sub1.title')} (*{getMessage('common.require')}) +

+ {getMessage('join.sub1.comment')} +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {getMessage('join.sub1.storeQcastNm')} * + +
+ +
+
+ {getMessage('join.sub1.storeQcastNmKana')} * + +
+ +
+
+ {getMessage('join.sub1.postCd')}/{getMessage('join.sub1.addr')} * + +
+
+ +
+
+ +
+
+
+ {getMessage('join.sub1.telNo')} * + +
+ +
+
+ {getMessage('join.sub1.fax')} * + +
+ +
+
{getMessage('join.sub1.bizNo')} +
+ +
+
+
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
{getMessage('join.sub1.storeQcastNm')} * - -
{getMessage('join.sub1.storeQcastNmKana')} * - -
- {getMessage('join.sub1.postCd')}/{getMessage('join.sub1.addr')} * - - - -
{getMessage('join.sub1.telNo')} * - -
{getMessage('join.sub1.fax')} * - -
- -
- ● {getMessage('join.sub2.title')} (*{getMessage('common.require')}) +
+
+
+

+ {getMessage('join.sub2.title')} (*{getMessage('common.require')}) +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {getMessage('join.sub2.userNm')} * + +
+ +
+
{getMessage('join.sub2.userNmKana')} +
+ +
+
+ {getMessage('join.sub2.userId')} * + +
+ +
+
+ {getMessage('join.sub2.email')} * + +
+ +
+
+ {getMessage('join.sub2.telNo')} * + +
+ +
+
+ {getMessage('join.sub2.fax')} * + +
+ +
+
{getMessage('join.sub2.category')} +
+ +
+
+
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{getMessage('join.sub2.userNm')} * - -
{getMessage('join.sub2.userNmKana')} * - -
{getMessage('join.sub2.userId')} * - -
{getMessage('join.sub2.email')} * - -
{getMessage('join.sub2.telNo')} * - -
{getMessage('join.sub2.fax')} * - -
{getMessage('join.sub2.category')} - -
- -
- ● {getMessage('join.sub3.title')} (*{getMessage('common.require')}) +
+ +
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
{getMessage('join.sub3.qtCompNm')} - -
- {getMessage('join.sub3.qtPostCd')}/{getMessage('join.sub3.qtAddr')} - - - -
{getMessage('join.sub3.qtEmail')} - -
{getMessage('join.sub3.qtTelNo')} - -
{getMessage('join.sub3.qtFax')} - -
-
-
- -
- + +
) } diff --git a/src/components/auth/JoinComplete.jsx b/src/components/auth/JoinComplete.jsx new file mode 100644 index 00000000..c876f20a --- /dev/null +++ b/src/components/auth/JoinComplete.jsx @@ -0,0 +1,44 @@ +'use client' + +import { useMessage } from '@/hooks/useMessage' +import { useRouter } from 'next/navigation' +import { useState } from 'react' +import Cookies from 'js-cookie' + +export default function JoinComplete() { + const router = useRouter() + + const { getMessage } = useMessage() + const [emailText, setEmailText] = useState(Cookies.get('joinEmail')) + + return ( + <> +
+
+
+
+
{getMessage('join.complete.title')}
+
{getMessage('join.complete.contents')}
+
+
+ {getMessage('join.complete.email_comment')}: {emailText} +
+
+
+ +
+
+
+
+
+ + ) +} diff --git a/src/components/auth/Login.jsx b/src/components/auth/Login.jsx index ef9919c7..28416284 100644 --- a/src/components/auth/Login.jsx +++ b/src/components/auth/Login.jsx @@ -1,62 +1,80 @@ 'use client' -import { useState } from 'react' +import { useState, useEffect } from 'react' +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 { redirect } from 'next/navigation' import { useMessage } from '@/hooks/useMessage' - -import { Button, Switch } from '@nextui-org/react' -import { useRecoilState } from 'recoil' import { globalLocaleStore } from '@/store/localeAtom' -import { modalContent, modalState } from '@/store/modalAtom' import { sessionStore } from '@/store/commonAtom' +import { useRouter } from 'next/navigation' + +import Cookies from 'js-cookie' + +import { useSearchParams } from 'next/navigation' export default function Login() { - const { patch } = useAxios() + // 자동 로그인 + const initParams = useSearchParams() + const autoLoginParam = initParams.get('autoLoginParam1') + useEffect(() => { + if (autoLoginParam) { + autoLoginProcess(autoLoginParam) + } + }, []) + const autoLoginProcess = async (autoLoginParam) => { + await promisePost({ url: '/api/login/v1.0/user/login/autoLoginDecryptData', data: { loginId: autoLoginParam } }) + .then((res) => { + if (res) { + if (res.data) { + post({ url: '/api/login/v1.0/user', data: { loginId: res.data } }).then((response) => { + if (response) { + const result = { ...response, storeLvl: response.groupId === '60000' ? '1' : '2', pwdInitYn: 'Y' } + setSession(result) + setSessionState(result) + router.push('/') + } else { + router.push('/login') + } + }) + } + } + }) + .catch((error) => { + router.push('/login') + }) + } + + const [userId, setUserId] = useState('') + const [checkId, setCheckId] = useState('') + const [checkEmail, setCheckEmail] = useState('') + + useEffect(() => { + if (Cookies.get('chkLoginId')) { + setUserId(Cookies.get('chkLoginId')) + } + }, []) + + const [chkLoginId, setChkLoginId] = useState(Cookies.get('chkLoginId') ? true : false) + const [passwordVisible, setPasswordVisible] = useState(false) + const router = useRouter() const { getMessage } = useMessage() const [globalLocaleState, setGlbalLocaleState] = useRecoilState(globalLocaleStore) const [sessionState, setSessionState] = useRecoilState(sessionStore) - const [isSelected, setIsSelected] = useState(globalLocaleState === 'ko' ? true : false) - const handleSelected = () => { - if (isSelected) { - setGlbalLocaleState('ja') - } else { - setGlbalLocaleState('ko') - } + const [passwordReset, setPasswordReset] = useState(1) - setIsSelected(!isSelected) - } + const { promisePost, promisePatch, post } = useAxios(globalLocaleState) // login process - const loginProcess = async (formData) => { - const param = { - // langCd: currentLocale - langCd: globalLocaleState, - lastEditUser: formData.get('id'), - loginId: formData.get('id'), - pwd: formData.get('password'), - } - - // await post({ url: '/api/login/v1.0/login', data: param }).then((res) => { - // if (res) { - // if (res.result.resultCode == 'S') { - // // console.log('res.data', res.data) - // // 비밀번호 초기화가 필요한 경우 - // // if (res.data.pwdInitYn != 'Y') { - // // alert('비밀번호 초기화가 필요한 경우') - // // } else { - // setSession(res.data) - // redirect('/') - // // } - // } else { - // alert(res.result.resultMsg) - // } - // } - // }) + const loginProcess = async (e) => { + e.preventDefault() + const formData = new FormData(e.target) + /////////////////////////////////////////////////////////// // 임시 로그인 처리 setSession({ userId: 'NEW016610', @@ -71,9 +89,10 @@ export default function Login() { telNo: '336610', fax: null, email: 't10t@naver.com', - pwdInitYn: 'N', + pwdInitYn: 'Y', + storeLvl: '1', + groupId: '60000', }) - setSessionState({ userId: 'NEW016610', saleStoreId: null, @@ -87,155 +106,230 @@ export default function Login() { telNo: '336610', fax: null, email: 't10t@naver.com', - pwdInitYn: 'N', + pwdInitYn: 'Y', + storeLvl: '1', + groupId: '60000', }) - - redirect('/') + if (chkLoginId) { + Cookies.set('chkLoginId', formData.get('id'), { expires: 7 }) + } else { + Cookies.remove('chkLoginId') + } + router.push('/') // 임시 로그인 처리 끝 + /////////////////////////////////////////////////////////// + + // 로그인 처리 시작 - ** 상단 임시 로그인 추후 삭제 필요 ** + // const param = { + // loginId: formData.get('id'), + // pwd: formData.get('password'), + // } + // await promisePost({ url: '/api/login/v1.0/login', data: param }) + // .then((res) => { + // if (res) { + // if (res.data.result.resultCode === 'S') { + // setSession(res.data.data) + // setSessionState(res.data.data) + // // ID SAVE 체크되어 있는 경우, 쿠키 저장 + // if (chkLoginId) { + // Cookies.set('chkLoginId', formData.get('id'), { expires: 7 }) + // } else { + // Cookies.remove('chkLoginId') + // } + // router.push('/') + // } else { + // alert(res.data.result.resultMsg) + // } + // } + // }) + // .catch((error) => { + // alert(error.response.data.message) + // }) } // 비밀번호 초기화 관련 - const [open, setOpen] = useRecoilState(modalState) - const [contents, setContent] = useRecoilState(modalContent) - - const initPasswordProcess = async (formData) => { + const initPasswordProcess = async () => { const param = { - langCd: currentLocale, - lastEditUser: formData.get('checkId'), - loginId: formData.get('checkId'), - email: formData.get('checkEmail'), + loginId: checkId, + email: checkEmail, } - - await patch({ url: '/api/login/v1.0/user/init-password', data: param }).then((res) => { - if (res) { - if (res.result.resultCode == 'S') { - alert(getMessage('login.init_password.complete_message')) - redirect('/login') - } else { - alert(res.result.resultMsg) - } - } + await promisePatch({ + url: '/api/login/v1.0/user/init-password', + data: param, }) + .then((res) => { + if (res) { + if (res.data.result.resultCode == 'S') { + alert(getMessage('login.init_password.complete_message')) + setCheckId('') + setCheckEmail('') + setPasswordReset(1) + } else { + alert(res.data.result.resultMsg) + } + } + }) + .catch((error) => { + alert(error.response.data.message) + }) } - const initPasswordContent = ( -
-
-

{getMessage('login.init_password.title')}

-

{getMessage('login.init_password.sub_title')}

-
- -
- -
-
- -
-
- -
-
- -
-
-

- -

-
-
- ) - return ( -
-
-
-

{getMessage('site.name')}

-

{getMessage('site.sub_name')}

-
+
+
+ + react + -
-
-
- -
- + {passwordReset === 1 && ( + <> +
+ +
+ {getMessage('site.name')} + {getMessage('site.sub_name')} +
+
+
+ { + setUserId(e.target.value) + }} + /> + +
+
+ { + setPasswordVisible(passwordVisible) + }} + /> + +
+
+ { + setChkLoginId(e.target.checked) + }} + /> + +
+
+ +
+
+ +
+
+ +
+
+ + {getMessage('login.guide.text')} +
+ {getMessage('login.guide.sub1')} {getMessage('login.guide.join.btn')} + {getMessage('login.guide.sub2')} +
+ + )} + {passwordReset === 2 && ( + <> +
+
+ {getMessage('login.init_password.title')} + {getMessage('login.init_password.sub_title')} +
+
+
+ { + setCheckId(e.target.value) + }} + /> + +
+
+ { + setCheckEmail(e.target.value) + }} + placeholder={getMessage('login.init_password.email.placeholder')} + /> + +
+
+ + +
- -
-
- -
-
- -
-
- -
- -
- - -

- -

- -
- - {isSelected ? 'Current Locale: KO' : 'Current Locale: JA'} - -
-
+ + )}
+
COPYRIGHT©2024 Hanwha Japan All Rights Reserved.
) } diff --git a/src/components/auth/NewLogin.jsx b/src/components/auth/NewLogin.jsx deleted file mode 100644 index 61c02abf..00000000 --- a/src/components/auth/NewLogin.jsx +++ /dev/null @@ -1,244 +0,0 @@ -'use client' - -import { useState, useRef, useEffect } from 'react' -import Image from 'next/image' -import Link from 'next/link' -import { redirect } from 'next/navigation' -import { useRecoilState } from 'recoil' -import { useAxios } from '@/hooks/useAxios' -import { setSession } from '@/lib/authActions' -import { useMessage } from '@/hooks/useMessage' -import { globalLocaleStore } from '@/store/localeAtom' -import { sessionStore } from '@/store/commonAtom' -import { modalContent, modalState } from '@/store/modalAtom' -import '@/styles/style.scss' -import { useRouter } from 'next/navigation' - -export default function NewLogin() { - const [passwordVisible, setPasswordVisible] = useState(false) - const passwordRef = useRef(null) - const router = useRouter() - - useEffect(() => { - setOpen(false) - }, []) - - useEffect(() => { - if (passwordVisible) { - passwordRef.current.type = 'text' - } else { - passwordRef.current.type = 'password' - } - }, [passwordVisible]) - - const { patch } = useAxios() - - const { getMessage } = useMessage() - const [globalLocaleState, setGlbalLocaleState] = useRecoilState(globalLocaleStore) - const [sessionState, setSessionState] = useRecoilState(sessionStore) - const [isSelected, setIsSelected] = useState(globalLocaleState === 'ko' ? true : false) - - const handleSelected = () => { - if (isSelected) { - setGlbalLocaleState('ja') - } else { - setGlbalLocaleState('ko') - } - - setIsSelected(!isSelected) - } - - // login process - const loginProcess = async (formData) => { - const param = { - // langCd: currentLocale - langCd: globalLocaleState, - lastEditUser: formData.get('id'), - loginId: formData.get('id'), - pwd: formData.get('password'), - } - - // await post({ url: '/api/login/v1.0/login', data: param }).then((res) => { - // if (res) { - // if (res.result.resultCode == 'S') { - // // console.log('res.data', res.data) - // // 비밀번호 초기화가 필요한 경우 - // // if (res.data.pwdInitYn != 'Y') { - // // alert('비밀번호 초기화가 필요한 경우') - // // } else { - // setSession(res.data) - // redirect('/') - // // } - // } else { - // alert(res.result.resultMsg) - // } - // } - // }) - - // 임시 로그인 처리 - setSession({ - userId: 'NEW016610', - saleStoreId: null, - name: null, - mail: null, - tel: null, - storeId: 'TEMP02', - userNm: 'ㅇㅇ6610', - userNmKana: '신규사용자 16610', - category: '인상6610', - telNo: '336610', - fax: null, - email: 't10t@naver.com', - pwdInitYn: 'Y', - }) - - setSessionState({ - userId: 'NEW016610', - saleStoreId: null, - name: null, - mail: null, - tel: null, - storeId: 'TEMP02', - userNm: 'ㅇㅇ6610', - userNmKana: '신규사용자 16610', - category: '인상6610', - telNo: '336610', - fax: null, - email: 't10t@naver.com', - pwdInitYn: 'Y', - }) - - // redirect('/') - router.push('/') - // 임시 로그인 처리 끝 - } - - // 비밀번호 초기화 관련 - const [open, setOpen] = useRecoilState(modalState) - const [contents, setContent] = useRecoilState(modalContent) - - const initPasswordProcess = async (formData) => { - const param = { - langCd: currentLocale, - lastEditUser: formData.get('checkId'), - loginId: formData.get('checkId'), - email: formData.get('checkEmail'), - } - - await patch({ url: '/api/login/v1.0/user/init-password', data: param }).then((res) => { - if (res) { - if (res.result.resultCode == 'S') { - alert(getMessage('login.init_password.complete_message')) - redirect('/login') - } else { - alert(res.result.resultMsg) - } - } - }) - } - - const initPasswordContent = ( -
-
-

{getMessage('login.init_password.title')}

-

{getMessage('login.init_password.sub_title')}

-
- -
- -
-
- -
-
- -
-
- -
-
-

- -

-
-
- ) - - return ( -
-
- - react - -
-
-
- Q.CAST III - 太陽光発電システム図面管理サイト -
-
-
- - -
-
- - -
-
- - -
-
- -
-
- パスワードの初期化 -
-
-
-
-
- 当サイトをご利用の際は、事前申請が必要です。 -
- IDがない方は ID申請 クリックしてください。 -
-
-
COPYRIGHT©2024 Hanwha Japan All Rights Reserved.
-
- ) -} diff --git a/src/components/common/context-menu/QContextMenu.jsx b/src/components/common/context-menu/QContextMenu.jsx index a86e40af..8e3e2314 100644 --- a/src/components/common/context-menu/QContextMenu.jsx +++ b/src/components/common/context-menu/QContextMenu.jsx @@ -27,7 +27,7 @@ export default function QContextMenu(props) { const handleContextMenu = (e) => { // e.preventDefault() //기존 contextmenu 막고 setContextMenu({ visible: true, x: e.pageX, y: e.pageY }) - console.log(111, canvasProps) + // console.log(111, canvasProps) canvasProps?.upperCanvasEl.removeEventListener('contextmenu', handleContextMenu) //한번 노출 후 이벤트 삭제 } diff --git a/src/components/common/pagination/QPagination.jsx b/src/components/common/pagination/QPagination.jsx new file mode 100644 index 00000000..7eb6638a --- /dev/null +++ b/src/components/common/pagination/QPagination.jsx @@ -0,0 +1,43 @@ +import usePagination from '@/hooks/usePagination' + +export default function QPagination(props) { + const { handleChangePage = () => {}, pagePerBlock = 10 } = props + const { currentPage, changePage, pageGroup, totalPages, pages, startPage, endPage, pageRange } = usePagination(props) + + const handlePage = (page) => { + handleChangePage(page) + changePage(page) + } + + return ( +
    +
  1. + +
  2. +
  3. + +
  4. + {pageRange.map((page) => ( +
  5. + +
  6. + ))} +
  7. + +
  8. +
  9. + +
  10. +
+ ) +} diff --git a/src/components/community/Archive.jsx b/src/components/community/Archive.jsx index dcfc737f..768bc500 100644 --- a/src/components/community/Archive.jsx +++ b/src/components/community/Archive.jsx @@ -1,7 +1,71 @@ +'use client' + +import Link from 'next/link' +import Image from 'next/image' + +import Search from '@/components/community/Search' +import ArchiveTable from '@/components/community/ArchiveTable' + +import { useEffect, useState } from 'react' +import { useResetRecoilState } from 'recoil' +import { useMessage } from '@/hooks/useMessage' + +import { searchState } from '@/store/boardAtom' + export default function Archive() { + const { getMessage } = useMessage() + const resetSearch = useResetRecoilState(searchState) + const [isInitialized, setIsInitialized] = useState(false) + + useEffect(() => { + resetSearch() + setIsInitialized(true) + }, []) + + if (!isInitialized) { + return null + } + + const boardType = { + boardTitle: getMessage('board.archive.title'), + subTitle: getMessage('board.archive.sub.title'), + clsCode: 'DOWN', + } + return ( <> -

Community Archive

+
+
+
    +
  • + + {getMessage('board.archive.title')} + +
  • +
+
    +
  • + + react + +
  • +
  • + {getMessage('header.menus.community')} +
  • +
  • + {getMessage('board.archive.title')} +
  • +
+
+
+
+
+
+ + +
+
+
) } diff --git a/src/components/community/ArchiveTable.jsx b/src/components/community/ArchiveTable.jsx new file mode 100644 index 00000000..ef6b303b --- /dev/null +++ b/src/components/community/ArchiveTable.jsx @@ -0,0 +1,103 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useRecoilState } from 'recoil' +import { useAxios } from '@/hooks/useAxios' + +import { searchState } from '@/store/boardAtom' +import { useMessage } from '@/hooks/useMessage' + +import { handleFileDown } from '@/util/board-utils' + +export default function ArchiveTable({ clsCode }) { + const { getMessage } = useMessage() + + // api 조회 관련 + const { get } = useAxios() + const [search, setSearch] = useRecoilState(searchState) + const [boardList, setBoardList] = useState([]) + + // 목록 조회 + useEffect(() => { + async function fetchData() { + const url = `/api/board/list` + const params = new URLSearchParams({ + schNoticeTpCd: 'QC', + schNoticeClsCd: clsCode, + schTitle: search.searchValue ? search.searchValue : '', + }) + const apiUrl = `${url}?${params.toString()}` + + const resultData = await get({ url: apiUrl }) + + if (resultData) { + if (resultData.result.code === 200) { + setBoardList(resultData.data) + if (resultData.data.length > 0) { + setSearch({ ...search, totalCount: resultData.data[0].totCnt }) + } else { + setSearch({ ...search, totalCount: 0 }) + } + } else { + alert(resultData.result.message) + } + } + } + + fetchData() + }, [search.searchValue]) + + // 상세 파일 목록 조회 + const handleDetailFileListDown = async (noticeNo) => { + const url = `/api/board/detail` + const params = new URLSearchParams({ + noticeNo: noticeNo, + }) + const apiUrl = `${url}?${params.toString()}` + + const resultData = await get({ url: apiUrl }) + + if (resultData) { + if (resultData.result.code === 200) { + const boardDetailFileList = resultData.data.listFile + + if (boardDetailFileList && Array.isArray(boardDetailFileList)) { + boardDetailFileList.forEach((boardFile) => { + handleFileDown(boardFile) + }) + } + } else { + alert(resultData.result.message) + } + } + } + + return ( + <> +
+ {boardList?.map((board) => ( +
+
+
+ {/* 번호 */} + {board.rowNumber} +
+
+ {/* 제목 */} + {board.title} +
+
+ {/* 등록일 */} + {getMessage('board.sub.updDt')} : {board.uptDt ? board.uptDt : board.regDt} +
+
+
+ {/* 첨부파일 */} + +
+
+ ))} +
+ + ) +} diff --git a/src/components/community/Faq.jsx b/src/components/community/Faq.jsx index 722741b3..17254f5c 100644 --- a/src/components/community/Faq.jsx +++ b/src/components/community/Faq.jsx @@ -1,7 +1,80 @@ +'use client' + +import Link from 'next/link' +import Image from 'next/image' + +import Search from '@/components/community/Search' +import Pagination from '@/components/community/Pagination' +import Table from '@/components/community/Table' + +import { useEffect, useState } from 'react' +import { useResetRecoilState, useRecoilValue, useRecoilState } from 'recoil' +import { useMessage } from '@/hooks/useMessage' + +import { searchState } from '@/store/boardAtom' + export default function Faq() { + const { getMessage } = useMessage() + const resetSearch = useResetRecoilState(searchState) + const [isInitialized, setIsInitialized] = useState(false) + + const search = useRecoilValue(searchState) + const [searchForm, setSearchForm] = useRecoilState(searchState) + + useEffect(() => { + if (search.mainFlag === 'N') { + resetSearch() + } else { + setSearchForm({ ...searchForm, mainFlag: 'N' }) + } + setIsInitialized(true) + }, []) + + if (!isInitialized) { + return null + } + + const boardType = { + boardTitle: getMessage('board.faq.title'), + subTitle: getMessage('board.faq.sub.title'), + clsCode: 'FAQ', + } + return ( <> -

Community FAQ

+
+
+
    +
  • + + {getMessage('board.faq.title')} + +
  • +
+
    +
  • + + react + +
  • +
  • + {getMessage('header.menus.community')} +
  • +
  • + {getMessage('board.faq.title')} +
  • +
+
+
+
+
+
+ + + + + + ) } diff --git a/src/components/community/Notice.jsx b/src/components/community/Notice.jsx index 2ab67c8e..55dfbad1 100644 --- a/src/components/community/Notice.jsx +++ b/src/components/community/Notice.jsx @@ -1,7 +1,72 @@ +'use client' + +import Link from 'next/link' +import Image from 'next/image' + +import Search from '@/components/community/Search' +import Pagination from '@/components/community/Pagination' +import Table from '@/components/community/Table' + +import { useEffect, useState } from 'react' +import { useResetRecoilState } from 'recoil' +import { useMessage } from '@/hooks/useMessage' + +import { searchState } from '@/store/boardAtom' + export default function Notice() { + const { getMessage } = useMessage() + const resetSearch = useResetRecoilState(searchState) + const [isInitialized, setIsInitialized] = useState(false) + + useEffect(() => { + resetSearch() + setIsInitialized(true) + }, []) + + if (!isInitialized) { + return null + } + + const boardType = { + boardTitle: getMessage('board.notice.title'), + subTitle: getMessage('board.notice.sub.title'), + clsCode: 'NOTICE', + } return ( <> -

Community Notice

+
+
+
    +
  • + + {getMessage('board.notice.title')} + +
  • +
+
    +
  • + + + +
  • +
  • + {getMessage('header.menus.community')} +
  • +
  • + {getMessage('board.notice.title')} +
  • +
+
+
+
+
+
+ +
+ + + + ) } diff --git a/src/components/community/Pagination.jsx b/src/components/community/Pagination.jsx new file mode 100644 index 00000000..938f5b0d --- /dev/null +++ b/src/components/community/Pagination.jsx @@ -0,0 +1,78 @@ +'use client' + +import { searchState } from '@/store/boardAtom' +import { useRecoilState, useRecoilValue } from 'recoil' +import { generateBlockPagination } from '@/util/board-utils' + +export default function Pagination() { + const search = useRecoilValue(searchState) + + const [searchForm, setSearchForm] = useRecoilState(searchState) + + const handlePagination = (pageNum) => { + setSearchForm({ ...searchForm, currentPage: pageNum }) + } + + const totalPages = Math.ceil(search.totalCount / search.pageBlock) > 0 ? Math.ceil(search.totalCount / search.pageBlock) : 1 + const allPages = generateBlockPagination(search.currentPage, totalPages, 10) + + return ( + <> +
    +
  1. + +
  2. +
  3. + +
  4. + + {/* 페이지목록 */} + {allPages.map((page, index) => { + return ( +
  5. + +
  6. + ) + })} + +
  7. + +
  8. +
  9. + +
  10. +
+ + ) +} diff --git a/src/components/community/Search.jsx b/src/components/community/Search.jsx new file mode 100644 index 00000000..26ca1883 --- /dev/null +++ b/src/components/community/Search.jsx @@ -0,0 +1,117 @@ +'use client' + +import { searchState } from '@/store/boardAtom' +import { useRecoilState, useRecoilValue } from 'recoil' +import { useState } from 'react' +import { useMessage } from '@/hooks/useMessage' + +export default function Search({ title = '', subTitle = '', isSelectUse = false }) { + const { getMessage } = useMessage() + + const search = useRecoilValue(searchState) + const [searchForm, setSearchForm] = useRecoilState(searchState) + + const [selectPageBlock, setSelectPageBlock] = useState(search.pageBlock) + const [searchValue, setSearchValue] = useState(search.searchValue) + const [searchView, setSearchView] = useState(search.searchValue ? true : false) + const [searchViewText, setSearchViewText] = useState(search.searchValue ? search.searchValue : '') + + // Enter 키 처리 + const handleKeyDown = (e) => { + if (e.key === 'Enter') { + handleSubmit(e) + } + } + + // 조회 값 처리 + const handleSearch = (text, block) => { + if (text !== '') { + setSearchView(true) + setSearchViewText(text) + setSearchForm({ ...searchForm, currentPage: 1, searchValue: text, pageBlock: block, searchFlag: true }) + } else { + setSearchView(false) + setSearchViewText('') + setSearchForm({ ...searchForm, currentPage: 1, searchValue: '', pageBlock: block, searchFlag: !searchForm.searchFlag }) + } + // 조회 후 값 비워주기 + setSearchValue('') + } + + const handleSubmit = (e) => { + e.preventDefault() + handleSearch(searchValue, selectPageBlock) + } + + return ( + <> +
+
+ { + setSearchValue(e.target.value) + }} + onKeyDown={handleKeyDown} + value={searchValue} + /> + +
+ {searchView && ( +
+ {isSelectUse ? ( + <> +
${searchViewText}`, `${search.totalCount}`]), + }} + >
+ + ) : ( + <> +
${searchViewText}`, `${search.totalCount}`]), + }} + >
+ + )} +
+ )} +
+
+
+

{subTitle}

+
    +
  • + {getMessage('board.sub.total')} {search.totalCount} +
  • +
+
+ {isSelectUse && ( +
+
+ +
+
+ )} +
+ + ) +} diff --git a/src/components/community/Table.jsx b/src/components/community/Table.jsx new file mode 100644 index 00000000..f943bc86 --- /dev/null +++ b/src/components/community/Table.jsx @@ -0,0 +1,110 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useRecoilState } from 'recoil' + +import { searchState } from '@/store/boardAtom' + +import { useAxios } from '@/hooks/useAxios' +import { useMessage } from '@/hooks/useMessage' + +import BoardDetailModal from '../community/modal/BoardDetailModal' + +export default function Table({ clsCode }) { + const { getMessage } = useMessage() + + // api 조회 관련 + const { get } = useAxios() + const [search, setSearch] = useRecoilState(searchState) + const [boardList, setBoardList] = useState([]) + + // 팝업 관련 + const [open, setOpen] = useState(false) + const [modalNoticeNo, setModalNoticeNo] = useState('') + + // 목록 조회 + useEffect(() => { + async function fetchData() { + const startRow = (search.currentPage - 1) * search.pageBlock > 0 ? (search.currentPage - 1) * search.pageBlock + 1 : 1 + const endRow = search.currentPage * search.pageBlock + + const url = `/api/board/list` + const params = new URLSearchParams({ + schNoticeTpCd: 'QC', + schNoticeClsCd: clsCode, + schTitle: search.searchValue ? search.searchValue : '', + startRow: startRow, + endRow: endRow, + }) + const apiUrl = `${url}?${params.toString()}` + + const resultData = await get({ url: apiUrl }) + + if (resultData) { + if (resultData.result.code === 200) { + if (resultData.data.length > 0) { + setBoardList(resultData.data) + setSearch({ ...search, totalCount: resultData.data[0].totCnt }) + } else { + setBoardList([]) + setSearch({ ...search, totalCount: 0 }) + } + } else { + alert(resultData.result.message) + } + } + } + + fetchData() + }, [search.currentPage, search.searchValue, search.pageBlock, search.searchFlag]) + + return ( + <> +
+
+ + + + + + + {boardList.length > 0 ? ( + boardList?.map((board) => ( + { + setOpen(true) + setModalNoticeNo(board.noticeNo) + }} + > + + + + + )) + ) : ( + + + + )} + +
+ {/* 번호 */} + {board.rowNumber} + + {/* 제목 */} +
+
{board.title}
+ {board.attachYn && } +
+
+ {/* 등록일 */} + {board.regDt.split(' ')[0]} +
+ {getMessage('common.message.no.data')} +
+
+ {open && } + + ) +} diff --git a/src/components/community/modal/BoardDetailModal.jsx b/src/components/community/modal/BoardDetailModal.jsx new file mode 100644 index 00000000..ec324966 --- /dev/null +++ b/src/components/community/modal/BoardDetailModal.jsx @@ -0,0 +1,77 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useAxios } from '@/hooks/useAxios' +import { handleFileDown } from '@/util/board-utils' + +export default function BoardDetailModal({ noticeNo, setOpen }) { + // api 조회 관련 + const { get } = useAxios() + const [boardDetail, setBoardDetail] = useState({}) + + useEffect(() => { + // 상세 조회 + const fetchDetail = async (noticeNo) => { + const url = `/api/board/detail` + const params = new URLSearchParams({ + noticeNo: noticeNo, + }) + const apiUrl = `${url}?${params.toString()}` + + const resultData = await get({ url: apiUrl }) + + if (resultData) { + if (resultData.result.code === 200) { + const boardDetail = resultData.data + setBoardDetail(boardDetail) + } else { + alert(resultData.result.message) + } + } + } + + fetchDetail(noticeNo) + }, []) + + return ( + <> +
+
+
+
+ +
+
+
+
{boardDetail.title}
+ + {boardDetail.listFile && ( +
+
첨부파일 목록
+ {boardDetail.listFile.map((boardFile) => ( +
+ +
+ ))} +
+ )} + +
{boardDetail.contents}
+
+
+
+
+
+ + ) +} diff --git a/src/components/fabric/QLine.js b/src/components/fabric/QLine.js index ae3c6c62..616143fe 100644 --- a/src/components/fabric/QLine.js +++ b/src/components/fabric/QLine.js @@ -75,7 +75,7 @@ export const QLine = fabric.util.createClass(fabric.Line, { const y2 = this.top + this.height * scaleY const dx = x2 - x1 const dy = y2 - y1 - this.length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(0)) + this.length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(1)) }, addLengthText() { @@ -150,7 +150,7 @@ export const QLine = fabric.util.createClass(fabric.Line, { getLength() { //10배 곱해진 값 return - return Number(this.length.toFixed(1) * 10) + return Number(this.length.toFixed(0) * 10) }, setViewLengthText(bool) { diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index 43661bf2..d554a700 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -121,13 +121,13 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { this.on('modified', (e) => { this.addLengthText() - if (this.arrow) { - drawDirectionArrow(this) - } + // if (this.arrow) { + // drawDirectionArrow(this) + // } }) this.on('selected', () => { - drawDirectionArrow(this) + // drawDirectionArrow(this) Object.keys(this.controls).forEach((controlKey) => { if (controlKey !== 'ml' && controlKey !== 'mr') { this.setControlVisible(controlKey, false) @@ -200,14 +200,32 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { const end = points[(i + 1) % points.length] const dx = end.x - start.x const dy = end.y - start.y - const length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(1)) * 10 + const length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(0)) * 10 - const midPoint = new fabric.Point((start.x + end.x) / 2, (start.y + end.y) / 2) + let midPoint + + switch (this.direction) { + case 'north': + midPoint = new fabric.Point((start.x + end.x) / 2, (start.y + end.y) / 2 - 30) + break + case 'west': + midPoint = new fabric.Point((start.x + end.x) / 2 - 30, (start.y + end.y) / 2) + break + case 'south': + midPoint = new fabric.Point((start.x + end.x) / 2, (start.y + end.y) / 2 + 30) + break + case 'east': + midPoint = new fabric.Point((start.x + end.x) / 2 + 30, (start.y + end.y) / 2) + break + default: + midPoint = new fabric.Point((start.x + end.x) / 2, (start.y + end.y) / 2) + break + } const degree = (Math.atan2(dy, dx) * 180) / Math.PI // Create new text object if it doesn't exist - const text = new fabric.Text(length.toFixed(0), { + const text = new fabric.Text(length.toString(), { left: midPoint.x, top: midPoint.y, fontSize: this.fontSize, diff --git a/src/components/floor-plan/CanvasFrame.jsx b/src/components/floor-plan/CanvasFrame.jsx index 3b823460..d6a4e179 100644 --- a/src/components/floor-plan/CanvasFrame.jsx +++ b/src/components/floor-plan/CanvasFrame.jsx @@ -1,19 +1,19 @@ 'use client' -import { useEffect, useRef, useState } from 'react' +import { useEffect, useRef } from 'react' import { useCanvas } from '@/hooks/useCanvas' import { useEvent } from '@/hooks/useEvent' import QContextMenu from '@/components/common/context-menu/QContextMenu' +import { useContextMenu } from '@/hooks/useContextMenu' import { useRecoilValue } from 'recoil' -import { currentMenuState } from '@/store/canvasAtom' -import { MENU } from '@/common/common' +import { currentObjectState } from '@/store/canvasAtom' export default function CanvasFrame({ plan }) { const canvasRef = useRef(null) const { canvas } = useCanvas('canvas') - const currentMenu = useRecoilValue(currentMenuState) - const [contextMenu, setContextMenu] = useState([[]]) + const { contextMenu, currentContextMenu, setCurrentContextMenu } = useContextMenu() + useEvent() const loadCanvas = () => { @@ -29,131 +29,9 @@ export default function CanvasFrame({ plan }) { useEffect(() => { loadCanvas() - }, [plan]) + }, [plan, canvas]) - useEffect(() => { - switch (currentMenu) { - case MENU.PLAN_DRAWING: - setContextMenu([ - [ - { - name: '그리드 이동', - }, - { - name: '그리드 복사', - }, - { - name: '그리드 색 변경', - }, - { - name: '삭제', - }, - { - name: '전체 삭제', - }, - ], - ]) - break - case MENU.ROOF_COVERING.EXTERIOR_WALL_LINE: - case MENU.ROOF_COVERING.ROOF_SHAPE_SETTINGS: - case MENU.ROOF_COVERING.ROOF_SHAPE_PASSIVITY_SETTINGS: - case MENU.ROOF_COVERING.ROOF_SHAPE_EDITING: - case MENU.ROOF_COVERING.HELP_LINE_DRAWING: - case MENU.ROOF_COVERING.EAVES_KERAVA_EDIT: - case MENU.ROOF_COVERING.MOVEMENT_SHAPE_UPDOWN: - case MENU.ROOF_COVERING.OUTLINE_EDIT_OFFSET: - case MENU.ROOF_COVERING.ROOF_SHAPE_ALLOC: - case MENU.ROOF_COVERING.DEFAULT: - console.log('지붕덮개') - setContextMenu([ - [ - { - name: '지붕재 배치', - }, - { - name: '지붕재 삭제', - }, - { - name: '지붕재 전체 삭제', - }, - { - name: '선택・이동', - }, - { - name: '외벽선 삭제', - }, - ], - [ - { - name: '사이즈 변경', - }, - { - name: '보조선 이동(M)', - }, - { - name: '보조선 복사(C)', - }, - { - name: '보조선 삭제(D)', - }, - { - name: '보조선 수직이등분선', - }, - { - name: '보조선 절삭', - }, - { - name: '보조선 전체 삭제', - }, - ], - ]) - break - case MENU.BATCH_CANVAS.SLOPE_SETTING: - case MENU.BATCH_CANVAS.BATCH_DRAWING: - case MENU.BATCH_CANVAS.SURFACE_SHAPE_BATCH: - case MENU.BATCH_CANVAS.OBJECT_BATCH: - case MENU.BATCH_CANVAS.ALL_REMOVE: - case MENU.BATCH_CANVAS.DEFAULT: - console.log('배치면') - setContextMenu([ - [ - { - name: '사이즈 변경', - }, - { - name: '삭제(D)', - }, - { - name: '이동(M)', - }, - { - name: '복사(C)', - }, - ], - [ - { - name: '지붕재 변경', - }, - { - name: '각 변 속성 변경', - }, - { - name: '흐름 방향 변경', - }, - ], - ]) - break - default: - console.log('default') - setContextMenu([]) - break - } - }, [currentMenu]) - - useEffect(() => { - console.log('currentMenu', currentMenu) - console.log('contextMenu', contextMenu) - }, [contextMenu]) + const onClickContextMenu = (index) => {} return (
@@ -162,11 +40,12 @@ export default function CanvasFrame({ plan }) { {contextMenu.map((menus, index) => (
    {menus.map((menu) => ( -
  • {menu.name}
  • +
  • setCurrentContextMenu(menu)}>{menu.name}
  • ))}
))} + {currentContextMenu?.component}
) } diff --git a/src/components/floor-plan/CanvasLayout.jsx b/src/components/floor-plan/CanvasLayout.jsx index 7a3e9323..7c84705c 100644 --- a/src/components/floor-plan/CanvasLayout.jsx +++ b/src/components/floor-plan/CanvasLayout.jsx @@ -1,108 +1,29 @@ 'use client' -import { useEffect, useState } from 'react' -import { useRecoilState, useRecoilValue } from 'recoil' +import { useContext, useEffect, useState } from 'react' +import { useRecoilValue } from 'recoil' import CanvasFrame from './CanvasFrame' import { useMessage } from '@/hooks/useMessage' import { useSwal } from '@/hooks/useSwal' import { usePlan } from '@/hooks/usePlan' import { globalLocaleStore } from '@/store/localeAtom' -import { currentCanvasPlanState, initCanvasPlansState, plansState } from '@/store/canvasAtom' import { sessionStore } from '@/store/commonAtom' +import { SessionContext } from '@/app/SessionProvider' export default function CanvasLayout() { + const { session } = useContext(SessionContext) + console.log('session >>> ', session) const [objectNo, setObjectNo] = useState('test123240822001') // 이후 삭제 필요 - const [planNum, setPlanNum] = useState(0) - const [currentCanvasPlan, setCurrentCanvasPlan] = useRecoilState(currentCanvasPlanState) - const [initCanvasPlans, setInitCanvasPlans] = useRecoilState(initCanvasPlansState) - const [plans, setPlans] = useRecoilState(plansState) - const globalLocaleState = useRecoilValue(globalLocaleStore) const sessionState = useRecoilValue(sessionStore) + const globalLocaleState = useRecoilValue(globalLocaleStore) const { getMessage } = useMessage() const { swalFire } = useSwal() - const { getCanvasByObjectNo, delCanvasById, checkModifiedCanvasPlan, saveCanvas } = usePlan() - - const handleCurrentPlan = (newCurrentId) => { - // console.log('currentPlan newCurrentId: ', newCurrentId) - - if (!currentCanvasPlan?.id || currentCanvasPlan.id !== newCurrentId) { - if (currentCanvasPlan?.id && checkModifiedCanvasPlan()) { - swalFire({ - html: getMessage('common.message.confirm.save') + `
${currentCanvasPlan.name}`, - type: 'confirm', - confirmFn: async () => { - await saveCanvas(sessionState.userId) - updateCurrentPlan(newCurrentId) - }, - denyFn: () => { - updateCurrentPlan(newCurrentId) - }, - }) - } else { - updateCurrentPlan(newCurrentId) - } - } - } - const updateCurrentPlan = (newCurrentId) => { - setPlans((plans) => - plans.map((plan) => { - return { ...plan, isCurrent: plan.id === newCurrentId } - }), - ) - } - useEffect(() => { - setCurrentCanvasPlan(plans.find((plan) => plan.isCurrent) || null) - }, [plans]) - - const handleDeletePlan = (e, id) => { - e.stopPropagation() // 이벤트 버블링 방지 - - if (initCanvasPlans.some((plan) => plan.id === id)) { - delCanvasById(id) - .then((res) => { - swalFire({ text: getMessage('common.message.delete') }) - // console.log('[DELETE] canvas-statuses res :::::::: %o', res) - setInitCanvasPlans((initCanvasPlans) => initCanvasPlans.filter((plan) => plan.id !== id)) - setPlans((plans) => plans.filter((plan) => plan.id !== id)) - }) - .catch((error) => { - swalFire({ text: error.message, icon: 'error' }) - // console.error('[DELETE] canvas-statuses res error :::::::: %o', error) - }) - } else { - setPlans((plans) => plans.filter((plan) => plan.id !== id)) - swalFire({ text: getMessage('common.message.delete') }) - } - - // 삭제 후 last 데이터에 포커싱 - const lastPlan = plans.filter((plan) => plan.id !== id).at(-1) - if (!lastPlan) { - setPlanNum(0) - setCurrentCanvasPlan(null) - } else if (id !== lastPlan.id) { - handleCurrentPlan(lastPlan.id) - } - } - - const addNewPlan = () => { - setPlans([...plans, { id: planNum, name: `Plan ${planNum + 1}`, objectNo: `${objectNo}` }]) - handleCurrentPlan(planNum) - setPlanNum(planNum + 1) - } + const { plans, loadCanvasPlanData, handleCurrentPlan, handleAddPlan, handleDeletePlan } = usePlan() useEffect(() => { - getCanvasByObjectNo(sessionState.userId, objectNo).then((res) => { - console.log('canvas 목록 ', res) - if (res.length > 0) { - setInitCanvasPlans(res) - setPlans(res) - handleCurrentPlan(res.at(-1).id) // last 데이터에 포커싱 - setPlanNum(res.length) - } else { - addNewPlan() - } - }) + console.log('loadCanvasPlanData 실행, sessionState.userId >>> ', sessionState.userId) + loadCanvasPlanData(sessionState.userId, objectNo) }, []) return ( @@ -110,13 +31,17 @@ export default function CanvasLayout() {
{plans.map((plan) => ( - ))}
- + {plans.length < 10 && ( + + )}
plan.isCurrent === true)} />
diff --git a/src/components/floor-plan/CanvasMenu.jsx b/src/components/floor-plan/CanvasMenu.jsx index eae0ea4e..c0cd09e5 100644 --- a/src/components/floor-plan/CanvasMenu.jsx +++ b/src/components/floor-plan/CanvasMenu.jsx @@ -19,6 +19,7 @@ import { MENU } from '@/common/common' import KO from '@/locales/ko.json' import JA from '@/locales/ja.json' import { settingModalFirstOptionsState } from '@/store/settingAtom' +import { placementShapeDrawingPointsState } from '@/store/placementShapeDrawingAtom' const canvasMenus = [ { index: 0, name: 'plan.menu.plan.drawing', icon: 'con00', title: MENU.PLAN_DRAWING }, @@ -48,6 +49,8 @@ export default function CanvasMenu(props) { setShowWallLineOffsetSettingModal, setShowRoofAllocationSettingModal, setShowBasicSettingModal, + setShowCircuitTrestleSettingModal, + setShowPropertiesSettingModal, } = props const [menuNumber, setMenuNumber] = useState(null) @@ -56,7 +59,8 @@ export default function CanvasMenu(props) { const [verticalHorizontalMode, setVerticalHorizontalMode] = useRecoilState(verticalHorizontalModeState) const [appMessageState, setAppMessageState] = useRecoilState(appMessageStore) const setCurrentMenu = useSetRecoilState(currentMenuState) - const setPoints = useSetRecoilState(outerLinePointsState) + const setOuterLinePoints = useSetRecoilState(outerLinePointsState) + const setPlacementPoints = useSetRecoilState(placementShapeDrawingPointsState) const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState) const [currentCanvasPlan, setcurrentCanvasPlan] = useRecoilState(currentCanvasPlanState) @@ -104,6 +108,8 @@ export default function CanvasMenu(props) { setShowRoofAllocationSettingModal, setShowObjectSettingModal, setShowBasicSettingModal, + setShowCircuitTrestleSettingModal, + setShowPropertiesSettingModal, type, } @@ -117,14 +123,14 @@ export default function CanvasMenu(props) { }, [menuNumber, type]) // 저장버튼(btn08) 클릭 시 호출되는 함수 - const handleSaveCanvas = () => { - swalFire({ - html: getMessage('common.message.confirm.save') + `
${currentCanvasPlan.name}`, - type: 'confirm', - confirmFn: () => { - saveCanvas(sessionState.userId) - }, - }) + const handleSaveCanvas = async () => { + // swalFire({ + // text: `${currentCanvasPlan.name} ` + getMessage('plan.message.confirm.save'), + // type: 'confirm', + // confirmFn: async () => { + await saveCanvas(sessionState.userId) + // }, + // }) } const onClickPlacementInitialMenu = () => { @@ -136,7 +142,8 @@ export default function CanvasMenu(props) { } const handleClear = () => { - setPoints([]) + setOuterLinePoints([]) + setPlacementPoints([]) canvas?.clear() } @@ -161,7 +168,11 @@ export default function CanvasMenu(props) {
    {canvasMenus.map((menu) => { return ( -
  • onClickNav(menu)}> +
  • onClickNav(menu)} + >
- + {canvasZoom}%
diff --git a/src/components/floor-plan/FloorPlan.jsx b/src/components/floor-plan/FloorPlan.jsx index d614a809..b8ea7784 100644 --- a/src/components/floor-plan/FloorPlan.jsx +++ b/src/components/floor-plan/FloorPlan.jsx @@ -27,6 +27,7 @@ import RoofShapePassivitySetting from '@/components/floor-plan/modal/roofShape/R import MovementSetting from '@/components/floor-plan/modal/movement/MovementSetting' import RoofAllocationSetting from '@/components/floor-plan/modal/roofAllocation/RoofAllocationSetting' import BasicSetting from '@/components/floor-plan/modal/basic/BasicSetting' +import CircuitTrestleSetting from '@/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting' export default function FloorPlan() { const [showCanvasSettingModal, setShowCanvasSettingModal] = useState(false) @@ -45,6 +46,7 @@ export default function FloorPlan() { const [showWallLineOffsetSettingModal, setShowWallLineOffsetSettingModal] = useState(false) const [showRoofAllocationSettingModal, setShowRoofAllocationSettingModal] = useState(false) const [showBasicSettingModal, setShowBasicSettingModal] = useState(false) + const [showCircuitTrestleSettingModal, setShowCircuitTrestleSettingModal] = useState(false) const globalLocaleState = useRecoilValue(globalLocaleStore) const { get } = useAxios(globalLocaleState) @@ -84,6 +86,8 @@ export default function FloorPlan() { setShowWallLineOffsetSettingModal, setShowRoofAllocationSettingModal, setShowBasicSettingModal, + setShowCircuitTrestleSettingModal, + setShowPropertiesSettingModal, } useEffect(() => { @@ -158,6 +162,7 @@ export default function FloorPlan() { {showObjectSettingModal && } {showPlacementSurfaceSettingModal && } {showBasicSettingModal && } + {showCircuitTrestleSettingModal && }
diff --git a/src/components/floor-plan/MenuDepth01.jsx b/src/components/floor-plan/MenuDepth01.jsx index c7642894..763dc92b 100644 --- a/src/components/floor-plan/MenuDepth01.jsx +++ b/src/components/floor-plan/MenuDepth01.jsx @@ -5,7 +5,7 @@ import { useEffect, useState } from 'react' import { MENU } from '@/common/common' import { currentMenuState } from '@/store/canvasAtom' import { useSetRecoilState } from 'recoil' - +import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch' export default function MenuDepth01(props) { const { setShowOutlineModal, @@ -23,10 +23,15 @@ export default function MenuDepth01(props) { setShowRoofAllocationSettingModal, setShowObjectSettingModal, setShowBasicSettingModal, + setShowCircuitTrestleSettingModal, + setShowPropertiesSettingModal, } = props const { getMessage } = useMessage() const [activeMenu, setActiveMenu] = useState() const setCurrentMenu = useSetRecoilState(currentMenuState) + + const { deleteAllSurfacesAndObjects } = useSurfaceShapeBatch() + const onClickMenu = ({ id, menu, name }) => { setActiveMenu(menu) setShowOutlineModal(menu === MENU.ROOF_COVERING.EXTERIOR_WALL_LINE) @@ -42,6 +47,8 @@ export default function MenuDepth01(props) { setShowWallLineOffsetSettingModal(id === 6) setShowRoofAllocationSettingModal(id === 7) setShowPlaceShapeDrawingModal(false) + setShowPropertiesSettingModal(false) + setShowCircuitTrestleSettingModal(false) } if (type === 'surface') { @@ -53,11 +60,17 @@ export default function MenuDepth01(props) { setShowMovementModal(false) setShowWallLineOffsetSettingModal(false) setShowRoofAllocationSettingModal(false) - + setShowPropertiesSettingModal(false) + setShowCircuitTrestleSettingModal(false) setShowSlopeSettingModal(id === 0) setShowPlaceShapeDrawingModal(id === 1) setShowPlacementSurfaceSettingModal(id === 2) setShowObjectSettingModal(id === 3) + + //배치면 전체 삭제 + if (id === 4) { + deleteAllSurfacesAndObjects() + } } if (type === 'module') { @@ -69,7 +82,9 @@ export default function MenuDepth01(props) { setShowMovementModal(false) setShowWallLineOffsetSettingModal(false) setShowRoofAllocationSettingModal(false) + setShowPropertiesSettingModal(false) setShowBasicSettingModal(id === 0) + setShowCircuitTrestleSettingModal(id === 1) } } diff --git a/src/components/floor-plan/modal/auxiliary/AuxiliaryDrawing.jsx b/src/components/floor-plan/modal/auxiliary/AuxiliaryDrawing.jsx index 1732f3f7..d0f87114 100644 --- a/src/components/floor-plan/modal/auxiliary/AuxiliaryDrawing.jsx +++ b/src/components/floor-plan/modal/auxiliary/AuxiliaryDrawing.jsx @@ -49,7 +49,7 @@ export default function AuxiliaryDrawing({ setShowAuxiliaryModal }) { handleFix, buttonAct, setButtonAct, - } = useAuxiliaryDrawing() + } = useAuxiliaryDrawing(setShowAuxiliaryModal) const outerLineProps = { length1, diff --git a/src/components/floor-plan/modal/auxiliary/AuxiliaryMove.jsx b/src/components/floor-plan/modal/auxiliary/AuxiliaryMove.jsx new file mode 100644 index 00000000..28674c07 --- /dev/null +++ b/src/components/floor-plan/modal/auxiliary/AuxiliaryMove.jsx @@ -0,0 +1,49 @@ +import { useMessage } from '@/hooks/useMessage' +import WithDraggable from '@/components/common/draggable/WithDraggable' + +export default function AuxiliaryMove({ setCurrentContextMenu }) { + const { getMessage } = useMessage() + return ( + +
+
+

補助線の移動

+ +
+
+
移動する方向を入力してください
+
+
+
+

長さ

+
+
+ +
+ mm +
+
+
+ +
+ mm +
+
+
+ + + + +
+
+
+
+ +
+
+
+
+ ) +} diff --git a/src/components/floor-plan/modal/auxiliary/AuxiliarySize.jsx b/src/components/floor-plan/modal/auxiliary/AuxiliarySize.jsx new file mode 100644 index 00000000..c562e8dd --- /dev/null +++ b/src/components/floor-plan/modal/auxiliary/AuxiliarySize.jsx @@ -0,0 +1,61 @@ +import { useMessage } from '@/hooks/useMessage' +import WithDraggable from '@/components/common/draggable/WithDraggable' + +export default function AuxiliarySize({ setCurrentContextMenu }) { + const { getMessage } = useMessage() + return ( + +
+
+

補助線サイズ変更

+ +
+
+
+
+ + +
+
+
+ +
+ mm +
+
+ 長さ +
+ +
+ mm +
+
+
+
+ + +
+
+
+ +
+ mm +
+
+ 長さ +
+ +
+ mm +
+
+
+ +
+
+
+
+ ) +} diff --git a/src/components/floor-plan/modal/basic/BasicSetting.jsx b/src/components/floor-plan/modal/basic/BasicSetting.jsx index d28e3ef5..555cdfcb 100644 --- a/src/components/floor-plan/modal/basic/BasicSetting.jsx +++ b/src/components/floor-plan/modal/basic/BasicSetting.jsx @@ -1,14 +1,18 @@ import { useMessage } from '@/hooks/useMessage' -import WithDraggable from '@/components/common/draggable/withDraggable' +import WithDraggable from '@/components/common/draggable/WithDraggable' import { useState } from 'react' import Orientation from '@/components/floor-plan/modal/basic/step/Orientation' import Module from '@/components/floor-plan/modal/basic/step/Module' +import PitchModule from '@/components/floor-plan/modal/basic/step/pitch/PitchModule' +import PitchPlacement from '@/components/floor-plan/modal/basic/step/pitch/PitchPlacement' import Placement from '@/components/floor-plan/modal/basic/step/Placement' +import { useRecoilState } from 'recoil' +import { canvasSettingState } from '@/store/canvasAtom' export default function BasicSetting({ setShowBasicSettingModal }) { const { getMessage } = useMessage() const [tabNum, setTabNum] = useState(1) - + const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState) return (
@@ -27,8 +31,13 @@ export default function BasicSetting({ setShowBasicSettingModal }) {
{getMessage('modal.module.basic.setting.module.placement')}
{tabNum === 1 && } - {tabNum === 2 && } - {tabNum === 3 && } + {/*배치면 초기설정 - 입력방법: 복시도 입력 || 실측값 입력*/} + {canvasSetting.roofSizeSet && canvasSetting.roofSizeSet != 3 && tabNum === 2 && } + {canvasSetting.roofSizeSet && canvasSetting.roofSizeSet != 3 && tabNum === 3 && } + + {/*배치면 초기설정 - 입력방법: 육지붕*/} + {canvasSetting.roofSizeSet && canvasSetting.roofSizeSet == 3 && tabNum === 2 && } + {canvasSetting.roofSizeSet && canvasSetting.roofSizeSet == 3 && tabNum === 3 && }
{tabNum !== 1 && ( @@ -36,7 +45,7 @@ export default function BasicSetting({ setShowBasicSettingModal }) { {getMessage('modal.module.basic.setting.prev')} )} - {tabNum !== 3 && } + {/*{tabNum !== 3 && }*/} {tabNum !== 3 && (
diff --git a/src/components/floor-plan/modal/basic/step/pitch/PitchModule.jsx b/src/components/floor-plan/modal/basic/step/pitch/PitchModule.jsx new file mode 100644 index 00000000..255833a8 --- /dev/null +++ b/src/components/floor-plan/modal/basic/step/pitch/PitchModule.jsx @@ -0,0 +1,91 @@ +import { useMessage } from '@/hooks/useMessage' +import QSelectBox from '@/components/common/select/QSelectBox' + +export default function PitchModule({}) { + const { getMessage } = useMessage() + const SelectOption01 = [{ name: '0' }, { name: '0' }, { name: '0' }, { name: '0' }] + const moduleData = { + header: [ + { name: getMessage('module'), width: 150, prop: 'module', type: 'color-box' }, + { + name: `${getMessage('높이')} (mm)`, + prop: 'height', + }, + { name: `${getMessage('width')} (mm)`, prop: 'width' }, + { name: `${getMessage('output')} (W)`, prop: 'output' }, + ], + rows: [ + { + module: { name: 'Re.RISE-G3 440', color: '#AA6768' }, + height: { name: '1134' }, + width: { name: '1722' }, + output: { name: '440' }, + }, + { + module: { + name: 'Re.RISE MS-G3 290', + color: '#67A2AA', + }, + height: { name: '1134' }, + width: { name: '1722' }, + output: { name: '240' }, + }, + ], + } + return ( + <> +
+
+
+ {getMessage('modal.module.basic.setting.module.setting')} +
+ +
+
+
+ + + + {moduleData.header.map((data) => ( + + ))} + + + + {moduleData.rows.map((row) => ( + <> + + {moduleData.header.map((header) => ( + <> + {header.type === 'color-box' && ( + + )} + {!header.type && header.type !== 'color-box' && } + + ))} + + + ))} + {Array.from({ length: 3 - moduleData.rows.length }).map((_, i) => ( + + + + + + + ))} + +
+ {data.name} +
+
+ + {row[header.prop].name} +
+
{row[header.prop].name}
+
+
+
+ + ) +} diff --git a/src/components/floor-plan/modal/basic/step/pitch/PitchPlacement.jsx b/src/components/floor-plan/modal/basic/step/pitch/PitchPlacement.jsx new file mode 100644 index 00000000..a2dcd556 --- /dev/null +++ b/src/components/floor-plan/modal/basic/step/pitch/PitchPlacement.jsx @@ -0,0 +1,105 @@ +import { useMessage } from '@/hooks/useMessage' + +export default function PitchPlacement() { + const { getMessage } = useMessage() + const moduleData = { + header: [ + { type: 'check', name: '', prop: 'check', width: 70 }, + { type: 'color-box', name: getMessage('module'), prop: 'module' }, + { type: 'text', name: `${getMessage('output')} (W)`, prop: 'output', width: 70 }, + ], + rows: [ + { + check: false, + module: { name: 'Re.RISE-G3 440', color: '#AA6768' }, + output: { name: '440' }, + }, + { + check: false, + module: { + name: 'Re.RISE MS-G3 290', + color: '#67A2AA', + }, + output: { name: '240' }, + }, + ], + } + return ( + <> +
+
+
+ + + + {moduleData.header.map((data) => ( + + ))} + + + + {moduleData.rows.map((row) => ( + <> + + {moduleData.header.map((header) => ( + <> + {header.type === 'color-box' && ( + + )} + {header.type === 'check' && ( + + )} + {header.type && header.type !== 'color-box' && header.type !== 'check' && } + + ))} + + + ))} + +
+ {data.type === 'check' ? ( +
+ + +
+ ) : ( + data.name + )} +
+
+ + {row[header.prop].name} +
+
+
+ + +
+
{row[header.prop].name}
+
+
+
+
+
+
+
+
{getMessage('modal.module.basic.setting.pitch.module.placement.standard.setting')}
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+ + ) +} diff --git a/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx b/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx new file mode 100644 index 00000000..f165bde0 --- /dev/null +++ b/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx @@ -0,0 +1,57 @@ +import WithDraggable from '@/components/common/draggable/WithDraggable' +import { useState } from 'react' +import PowerConditionalSelect from '@/components/floor-plan/modal/circuitTrestle/step/PowerConditionalSelect' +import CircuitAllocation from '@/components/floor-plan/modal/circuitTrestle/step/CircuitAllocation' +import StepUp from '@/components/floor-plan/modal/circuitTrestle/step/StepUp' +import { useMessage } from '@/hooks/useMessage' + +export default function CircuitTrestleSetting({ setShowCircuitTrestleSettingModal }) { + const { getMessage } = useMessage() + const [tabNum, setTabNum] = useState(1) + const [circuitAllocationType, setCircuitAllocationType] = useState(1) + const circuitProps = { + circuitAllocationType, + setCircuitAllocationType, + } + return ( + +
+
+

{getMessage('modal.circuit.trestle.setting')}

+ +
+
+
+
+ {getMessage('modal.circuit.trestle.setting.power.conditional.select')} +
+ +
+ {getMessage('modal.circuit.trestle.setting.circuit.allocation')} +
+ +
{getMessage('modal.circuit.trestle.setting.step.up.allocation')}
+
+ {tabNum === 1 && } + {tabNum === 2 && } + {tabNum === 3 && } +
+ {tabNum !== 1 && ( + + )} + {tabNum !== 3 && ( + + )} + {tabNum === 3 && } +
+
+
+
+ ) +} diff --git a/src/components/floor-plan/modal/circuitTrestle/step/CircuitAllocation.jsx b/src/components/floor-plan/modal/circuitTrestle/step/CircuitAllocation.jsx new file mode 100644 index 00000000..b980eeb4 --- /dev/null +++ b/src/components/floor-plan/modal/circuitTrestle/step/CircuitAllocation.jsx @@ -0,0 +1,25 @@ +import AutoCircuitAllocation from '@/components/floor-plan/modal/circuitTrestle/step/type/AutoCircuitAllocation' +import PassivityCircuitAllocation from '@/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation' +import { useMessage } from '@/hooks/useMessage' + +export default function CircuitAllocation(props) { + const { getMessage } = useMessage() + const { circuitAllocationType, setCircuitAllocationType } = props + return ( + <> +
+ + +
+
+
{getMessage('modal.circuit.trestle.setting.circuit.allocation')}
+ {circuitAllocationType === 1 && } + {circuitAllocationType === 2 && } +
+ + ) +} diff --git a/src/components/floor-plan/modal/circuitTrestle/step/PowerConditionalSelect.jsx b/src/components/floor-plan/modal/circuitTrestle/step/PowerConditionalSelect.jsx new file mode 100644 index 00000000..9eadeb7f --- /dev/null +++ b/src/components/floor-plan/modal/circuitTrestle/step/PowerConditionalSelect.jsx @@ -0,0 +1,146 @@ +import QSelectBox from '@/components/common/select/QSelectBox' +import { useMessage } from '@/hooks/useMessage' +import { useState } from 'react' + +const SelectOption01 = [{ name: '0' }, { name: '0' }, { name: '0' }, { name: '0' }] + +export default function PowerConditionalSelect({ setTabNum }) { + const { getMessage } = useMessage() + const [selectedRowIndex, setSelectedRowIndex] = useState(null) + const [powerConditions, setPowerConditions] = useState([]) + const seriesData = { + header: [ + { name: getMessage('명칭'), width: '15%', prop: 'name', type: 'color-box' }, + { + name: `${getMessage('modal.circuit.trestle.setting.power.conditional.select.rated.output')} (kW)`, + width: '10%', + prop: 'ratedOutput', + }, + { + name: `${getMessage('modal.circuit.trestle.setting.power.conditional.select.circuit.amount')}`, + width: '10%', + prop: 'circuitAmount', + }, + { + name: `${getMessage('modal.circuit.trestle.setting.power.conditional.select.max.connection')}`, + width: '10%', + prop: 'maxConnection', + }, + { + name: `${getMessage('modal.circuit.trestle.setting.power.conditional.select.max.overload')}`, + width: '10%', + prop: 'maxOverload', + }, + { + name: `${getMessage('modal.circuit.trestle.setting.power.conditional.select.output.current')}`, + width: '10%', + prop: 'outputCurrent', + }, + ], + rows: [ + { + name: { name: 'PCSオプションマスター', color: '#AA6768' }, + ratedOutput: { name: '2' }, + circuitAmount: { name: '2' }, + maxConnection: { name: '-' }, + maxOverload: { name: '-' }, + outputCurrent: { name: '-' }, + }, + { + name: { name: 'HQJP-KA40-5', color: '#AA6768' }, + ratedOutput: { name: '2' }, + circuitAmount: { name: '2' }, + maxConnection: { name: '-' }, + maxOverload: { name: '-' }, + outputCurrent: { name: '-' }, + }, + { + name: { name: 'Re.RISE-G3 440', color: '#AA6768' }, + ratedOutput: { name: '2' }, + circuitAmount: { name: '2' }, + maxConnection: { name: '-' }, + maxOverload: { name: '-' }, + outputCurrent: { name: '-' }, + }, + ], + } + return ( + <> +
+ 分類 (余剰) +
+ +
+ 寒冷地仕様 +
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+ + + + {seriesData.header.map((header) => ( + + ))} + + + + {seriesData.rows.map((row, index) => ( + setSelectedRowIndex(index)} className={index === selectedRowIndex ? 'on' : ''}> + {seriesData.header.map((header) => ( + + ))} + + ))} + +
+ {header.name} +
{row[header.prop].name}
+
+
+
+ +
+
+ + HQJP-KA40-5 + + + HQJP-KA40-5 + + + HQJP-KA40-5 + +
+
+
+
+
+ + +
+
+ + +
+
+ + ) +} diff --git a/src/components/floor-plan/modal/circuitTrestle/step/StepUp.jsx b/src/components/floor-plan/modal/circuitTrestle/step/StepUp.jsx new file mode 100644 index 00000000..6a11ed71 --- /dev/null +++ b/src/components/floor-plan/modal/circuitTrestle/step/StepUp.jsx @@ -0,0 +1,414 @@ +import QSelectBox from '@/components/common/select/QSelectBox' +import { useMessage } from '@/hooks/useMessage' + +const SelectOption01 = [{ name: '0' }, { name: '0' }, { name: '0' }, { name: '0' }] + +export default function StepUp({}) { + const { getMessage } = useMessage() + return ( + <> +
+
{getMessage('modal.circuit.trestle.setting.step.up.allocation')}
+
+
+
+
+
HQJP-KA55-5
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
シリアル枚数総回路数
100
100
100
100
100
+
+
+
+
+
接続する
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
名称回路数昇圧回路数
KTN-CBD4C40
KTN-CBD4C40
KTN-CBD4C40
+
+
+
+ +
+
+ + HQJP-KA40-5 + +
+
+
+
+
昇圧オプション
+
+ + + + + + + + + + + + + + + + + +
名称昇圧回路数
--
--
+
+
+
+ +
+
+ + HQJP-KA40-5 + +
+
+
+
+
+ 綿調道区分 +
+ +
+ 回路 + (二重昇圧回路数 +
+ +
+ 回路) +
+
+
+
+
+
+
HQJP-KA55-5
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
シリアル枚数総回路数
100
100
100
100
100
+
+
+
+
+
接続する
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
名称回路数昇圧回路数
KTN-CBD4C40
KTN-CBD4C40
KTN-CBD4C40
+
+
+
+ +
+
+ + HQJP-KA40-5 + +
+
+
+
+
昇圧オプション
+
+ + + + + + + + + + + + + + + + + +
名称昇圧回路数
--
--
+
+
+
+ +
+
+ + HQJP-KA40-5 + +
+
+
+
+
+ 綿調道区分 +
+ +
+ 回路 + (二重昇圧回路数 +
+ +
+ 回路) +
+
+
+
+
+
+
HQJP-KA55-5
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
シリアル枚数総回路数
100
100
100
100
100
+
+
+
+
+
接続する
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
名称回路数昇圧回路数
KTN-CBD4C40
KTN-CBD4C40
KTN-CBD4C40
+
+
+
+ +
+
+ + HQJP-KA40-5 + +
+
+
+
+
昇圧オプション
+
+ + + + + + + + + + + + + + + + + +
名称昇圧回路数
--
--
+
+
+
+ +
+
+ + HQJP-KA40-5 + +
+
+
+
+
+ 綿調道区分 +
+ +
+ 回路 + (二重昇圧回路数 +
+ +
+ 回路) +
+
+
+
+
+
+ + モニターの選択 + +
+ +
+
+
+
+ {/*
*/} + {/* */} + {/* */} + {/*
*/} + + ) +} diff --git a/src/components/floor-plan/modal/circuitTrestle/step/type/AutoCircuitAllocation.jsx b/src/components/floor-plan/modal/circuitTrestle/step/type/AutoCircuitAllocation.jsx new file mode 100644 index 00000000..026bbaee --- /dev/null +++ b/src/components/floor-plan/modal/circuitTrestle/step/type/AutoCircuitAllocation.jsx @@ -0,0 +1,17 @@ +import { useMessage } from '@/hooks/useMessage' + +export default function AutoCircuitAllocation() { + const { getMessage } = useMessage() + return ( +
+
+
+
+ + +
+
+
+
+ ) +} diff --git a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx new file mode 100644 index 00000000..5233d992 --- /dev/null +++ b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx @@ -0,0 +1,104 @@ +import { useMessage } from '@/hooks/useMessage' + +export default function PassivityCircuitAllocation() { + const { getMessage } = useMessage() + const moduleData = { + header: [ + { name: getMessage('屋根面'), prop: 'roofShape' }, + { + name: getMessage('Q.TRON M-G2'), + prop: 'moduleName', + }, + { + name: getMessage('発電量 (kW)'), + prop: 'powerGeneration', + }, + ], + rows: [ + { + roofShape: { name: 'M 1' }, + moduleName: { name: '8' }, + powerGeneration: { name: '3,400' }, + }, + { + roofShape: { name: 'M 1' }, + moduleName: { name: '8' }, + powerGeneration: { name: '3,400' }, + }, + ], + } + return ( + <> +
+
+
{getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity')}
+
{getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.info')}
+
+ + + + {moduleData.header.map((header) => ( + + ))} + + + + {moduleData.rows.map((row, index) => ( + + {moduleData.header.map((header) => ( + + ))} + + ))} + +
{header.name}
{row[header.prop].name}
+
+
+
+
+
+
+
+
{getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.selected.power.conditional')}
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+ + {getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.circuit.num')} + +
+ +
+
+
+
+ + + +
+
+ + ) +} diff --git a/src/components/floor-plan/modal/eavesGable/EavesGableEdit.jsx b/src/components/floor-plan/modal/eavesGable/EavesGableEdit.jsx index 1267fe98..bab1e696 100644 --- a/src/components/floor-plan/modal/eavesGable/EavesGableEdit.jsx +++ b/src/components/floor-plan/modal/eavesGable/EavesGableEdit.jsx @@ -10,7 +10,7 @@ import { useEavesGableEdit } from '@/hooks/roofcover/useEavesGableEdit' export default function EavesGableEdit({ setShowEavesGableEditModal }) { const { getMessage } = useMessage() - const { type, setType, buttonMenu, TYPES, pitchRef, offsetRef, widthRef, radioTypeRef } = useEavesGableEdit() + const { type, setType, buttonMenu, TYPES, pitchRef, offsetRef, widthRef, radioTypeRef } = useEavesGableEdit(setShowEavesGableEditModal) const eavesProps = { pitchRef, offsetRef, diff --git a/src/components/floor-plan/modal/object/ObjectSetting.jsx b/src/components/floor-plan/modal/object/ObjectSetting.jsx index c1405e1d..d5fe934e 100644 --- a/src/components/floor-plan/modal/object/ObjectSetting.jsx +++ b/src/components/floor-plan/modal/object/ObjectSetting.jsx @@ -1,6 +1,14 @@ +'use client' + +import { useState, useRef, useEffect } from 'react' +import { useRecoilValue } from 'recoil' import { useMessage } from '@/hooks/useMessage' +import { useEvent } from '@/hooks/useEvent' +import { canvasState } from '@/store/canvasAtom' +import { useSwal } from '@/hooks/useSwal' +import { useObjectBatch } from '@/hooks/object/useObjectBatch' +import { POLYGON_TYPE } from '@/common/common' import WithDraggable from '@/components/common/draggable/WithDraggable' -import { useState } from 'react' import OpenSpace from '@/components/floor-plan/modal/object/type/OpenSpace' import Shadow from '@/components/floor-plan/modal/object/type/Shadow' import TriangleDormer from '@/components/floor-plan/modal/object/type/TriangleDormer' @@ -8,7 +16,56 @@ import PentagonDormer from '@/components/floor-plan/modal/object/type/PentagonDo export default function ObjectSetting({ setShowObjectSettingModal }) { const { getMessage } = useMessage() + const canvas = useRecoilValue(canvasState) const [buttonAct, setButtonAct] = useState(1) + const { swalFire } = useSwal() + const { applyOpeningAndShadow, applyDormers } = useObjectBatch() + + const surfaceShapePolygons = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) + + //오브젝트 배치로 넘어오면 면형상 선택 불가 + useEffect(() => { + canvas.discardActiveObject() + surfaceShapePolygons.forEach((obj) => { + obj.set({ selectable: false }) + }) + }, []) + + /** + * 개구배치, 그림자배치 + */ + const objectPlacement = { + typeRef: useRef([]), //프리입력, 치수입력 + widthRef: useRef(null), + heightRef: useRef(null), + isCrossRef: useRef(null), + } + + const dormerPlacement = { + widthRef: useRef(null), + heightRef: useRef(null), + pitchRef: useRef(null), + offsetRef: useRef(null), + directionRef: useRef(null), + } + + const applyObject = () => { + // if (surfaceShapePolygons.length === 0) { + // swalFire({ text: '지붕이 없어요 지붕부터 그리세요', icon: 'error' }) + // return + // } + + //개구배치, 그림자배치 + + console.log(surfaceShapePolygons) + + if (buttonAct === 1 || buttonAct === 2) { + applyOpeningAndShadow(objectPlacement, buttonAct, surfaceShapePolygons) + } else { + applyDormers(dormerPlacement, buttonAct, surfaceShapePolygons) + } + } + const buttonMenu = [ { id: 1, name: getMessage('modal.object.setting.type.open.space.placement') }, { id: 2, name: getMessage('modal.object.setting.type.shadow.placement') }, @@ -34,13 +91,20 @@ export default function ObjectSetting({ setShowObjectSettingModal }) {
{getMessage('setting')}
- {buttonAct === 1 && } - {buttonAct === 2 && } - {buttonAct === 3 && } - {buttonAct === 4 && } + {buttonAct === 1 && } + {buttonAct === 2 && } + {buttonAct === 3 && } + {buttonAct === 4 && }
- +
diff --git a/src/components/floor-plan/modal/object/type/OpenSpace.jsx b/src/components/floor-plan/modal/object/type/OpenSpace.jsx index 1c79dc09..ab4d287d 100644 --- a/src/components/floor-plan/modal/object/type/OpenSpace.jsx +++ b/src/components/floor-plan/modal/object/type/OpenSpace.jsx @@ -1,18 +1,45 @@ +import { forwardRef, useState, useEffect } from 'react' import { useMessage } from '@/hooks/useMessage' +import { INPUT_TYPE } from '@/common/common' -export default function OpenSpace() { +const OpenSpace = forwardRef((props, refs) => { const { getMessage } = useMessage() + const [selectedType, setSelectedType] = useState(INPUT_TYPE.FREE) + + useEffect(() => { + if (selectedType === INPUT_TYPE.FREE) { + refs.widthRef.current.value = 0 + refs.heightRef.current.value = 0 + } + }, [selectedType]) + + //체크하면 값바꿈 return (
- + (refs.typeRef.current[0] = el)} + onClick={() => setSelectedType(INPUT_TYPE.FREE)} + />
- + (refs.typeRef.current[1] = el)} + onClick={() => setSelectedType(INPUT_TYPE.DIMENSION)} + />
@@ -24,7 +51,13 @@ export default function OpenSpace() {
- +
mm
@@ -35,7 +68,13 @@ export default function OpenSpace() {
- +
mm
@@ -45,9 +84,11 @@ export default function OpenSpace() {
- +
) -} +}) + +export default OpenSpace diff --git a/src/components/floor-plan/modal/object/type/Shadow.jsx b/src/components/floor-plan/modal/object/type/Shadow.jsx index bef52631..0945d78c 100644 --- a/src/components/floor-plan/modal/object/type/Shadow.jsx +++ b/src/components/floor-plan/modal/object/type/Shadow.jsx @@ -1,18 +1,45 @@ +import { forwardRef, useState, useEffect } from 'react' import { useMessage } from '@/hooks/useMessage' +import { INPUT_TYPE } from '@/common/common' -export default function Shadow() { +const Shadow = forwardRef((props, refs) => { const { getMessage } = useMessage() + + const [selectedType, setSelectedType] = useState(INPUT_TYPE.FREE) + + useEffect(() => { + if (selectedType === INPUT_TYPE.FREE) { + refs.widthRef.current.value = 0 + refs.heightRef.current.value = 0 + } + }, [selectedType]) + return (
- + (refs.typeRef.current[0] = el)} + onClick={() => setSelectedType(INPUT_TYPE.FREE)} + />
- + (refs.typeRef.current[1] = el)} + onClick={() => setSelectedType(INPUT_TYPE.DIMENSION)} + />
@@ -24,7 +51,13 @@ export default function Shadow() {
- +
mm
@@ -35,7 +68,13 @@ export default function Shadow() {
- +
mm
@@ -46,4 +85,6 @@ export default function Shadow() {
) -} +}) + +export default Shadow diff --git a/src/components/floor-plan/modal/object/type/TriangleDormer.jsx b/src/components/floor-plan/modal/object/type/TriangleDormer.jsx index 8cfa55e4..9ab7712d 100644 --- a/src/components/floor-plan/modal/object/type/TriangleDormer.jsx +++ b/src/components/floor-plan/modal/object/type/TriangleDormer.jsx @@ -1,8 +1,18 @@ import Image from 'next/image' import { useMessage } from '@/hooks/useMessage' +import { forwardRef, useState } from 'react' -export default function TriangleDormer() { +const TriangleDormer = forwardRef((props, refs) => { const { getMessage } = useMessage() + const [direction, setDirection] = useState('down') + + refs.directionRef.current = direction + + const getDirection = (e) => { + setDirection(e.target.value) + refs.directionRef.current = e.target.value + } + return ( <>
@@ -18,7 +28,7 @@ export default function TriangleDormer() {
- +
mm
@@ -29,18 +39,7 @@ export default function TriangleDormer() {
- -
- mm -
-
-
-
-
{getMessage('width')}
-
-
-
- +
mm
@@ -51,7 +50,7 @@ export default function TriangleDormer() {
- +
@@ -69,13 +68,15 @@ export default function TriangleDormer() { {getMessage('commons.east')} {getMessage('commons.south')} {getMessage('commons.west')} - - - - + + + +
) -} +}) + +export default TriangleDormer diff --git a/src/components/floor-plan/modal/outerlinesetting/PropertiesSetting.jsx b/src/components/floor-plan/modal/outerlinesetting/PropertiesSetting.jsx index 248567e3..d26b2ef3 100644 --- a/src/components/floor-plan/modal/outerlinesetting/PropertiesSetting.jsx +++ b/src/components/floor-plan/modal/outerlinesetting/PropertiesSetting.jsx @@ -6,7 +6,7 @@ export default function PropertiesSetting(props) { const { getMessage } = useMessage() const { setShowPropertiesSettingModal } = props - const { handleSetEaves, handleSetGable, handleRollback, handleFix, closeModal } = usePropertiesSetting() + const { handleSetEaves, handleSetGable, handleRollback, handleFix, closeModal } = usePropertiesSetting(setShowPropertiesSettingModal) return ( diff --git a/src/components/floor-plan/modal/outerlinesetting/WallLineSetting.jsx b/src/components/floor-plan/modal/outerlinesetting/WallLineSetting.jsx index 54bd6844..975a582a 100644 --- a/src/components/floor-plan/modal/outerlinesetting/WallLineSetting.jsx +++ b/src/components/floor-plan/modal/outerlinesetting/WallLineSetting.jsx @@ -39,7 +39,7 @@ export default function WallLineSetting(props) { outerLineDiagonalLengthRef, handleRollback, handleFix, - } = useOuterLineWall() + } = useOuterLineWall(setShowOutlineModal) const outerLineProps = { length1, @@ -171,7 +171,7 @@ export default function WallLineSetting(props) {
- {types.map((type) => ( - ))} @@ -145,8 +150,12 @@ export default function PlacementShapeDrawing({ setShowPlaceShapeDrawingModal })
- - + +
diff --git a/src/components/floor-plan/modal/placementSurface/PlacementSurfaceSetting.jsx b/src/components/floor-plan/modal/placementSurface/PlacementSurfaceSetting.jsx index d2c3022a..e667667e 100644 --- a/src/components/floor-plan/modal/placementSurface/PlacementSurfaceSetting.jsx +++ b/src/components/floor-plan/modal/placementSurface/PlacementSurfaceSetting.jsx @@ -4,17 +4,29 @@ import { useEffect, useState, useRef } from 'react' import Image from 'next/image' import PlacementSurface from '@/components/floor-plan/modal/placementSurface/PlacementSurface' import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch' +import { useRecoilValue } from 'recoil' +import { canvasState } from '@/store/canvasAtom' +import { POLYGON_TYPE } from '@/common/common' export default function PlacementSurfaceSetting({ setShowPlacementSurfaceSettingModal }) { const { getMessage } = useMessage() - const [selectedType, setSelectedType] = useState() const [rotate, setRotate] = useState(0) const [xInversion, setXInversion] = useState(false) const [yInversion, setYInversion] = useState(false) + const canvas = useRecoilValue(canvasState) const { applySurfaceShape } = useSurfaceShapeBatch() + const surfaceShapePolygons = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) + + //오브젝트 배치에서 넘어오면 면형상 선택 가능 + useEffect(() => { + surfaceShapePolygons.forEach((obj) => { + obj.set({ selectable: true }) + }) + }, []) + const surfaceRefs = { length1: useRef(null), length2: useRef(null), diff --git a/src/components/floor-plan/modal/roofAllocation/RoofAllocationSetting.jsx b/src/components/floor-plan/modal/roofAllocation/RoofAllocationSetting.jsx index d42f7fb2..72599bbe 100644 --- a/src/components/floor-plan/modal/roofAllocation/RoofAllocationSetting.jsx +++ b/src/components/floor-plan/modal/roofAllocation/RoofAllocationSetting.jsx @@ -2,76 +2,12 @@ import { useMessage } from '@/hooks/useMessage' import WithDraggable from '@/components/common/draggable/WithDraggable' import { useState } from 'react' import QSelectBox from '@/components/common/select/QSelectBox' +import { useRoofAllocationSetting } from '@/hooks/roofcover/useRoofAllocationSetting' export default function RoofAllocationSetting({ setShowRoofAllocationSettingModal }) { const { getMessage } = useMessage() - const [selectedRoofMaterial, setSelectedRoofMaterial] = useState(null) - const [values, setValues] = useState([ - { - id: '1', - type: 'A', - roofMaterial: { name: '기와1' }, - width: { name: '200' }, - length: { name: '250' }, - rafter: { name: '300' }, - alignType: 'stairs', - }, - ]) - - 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 onAddRoofMaterial = () => { - setValues([...values, selectedRoofMaterial]) - } - - const onDeleteRoofMaterial = (id) => { - setValues(values.filter((value) => value.id !== id)) - } + const { handleSave, onAddRoofMaterial, onDeleteRoofMaterial, values, roofMaterials, selectedRoofMaterial, setSelectedRoofMaterial } = + useRoofAllocationSetting(setShowRoofAllocationSettingModal) return ( @@ -103,7 +39,7 @@ export default function RoofAllocationSetting({ setShowRoofAllocationSettingModa {values.map((value, index) => (
- +
@@ -213,7 +149,9 @@ export default function RoofAllocationSetting({ setShowRoofAllocationSettingModa ))}
- +
diff --git a/src/components/floor-plan/modal/roofShape/RoofShapePassivitySetting.jsx b/src/components/floor-plan/modal/roofShape/RoofShapePassivitySetting.jsx index ebdc6bb9..ec1684a6 100644 --- a/src/components/floor-plan/modal/roofShape/RoofShapePassivitySetting.jsx +++ b/src/components/floor-plan/modal/roofShape/RoofShapePassivitySetting.jsx @@ -1,18 +1,29 @@ -import { useState } from 'react' import WithDraggable from '@/components/common/draggable/WithDraggable' import Eaves from '@/components/floor-plan/modal/roofShape/passivity/Eaves' import Gable from '@/components/floor-plan/modal/roofShape/passivity/Gable' import Shed from '@/components/floor-plan/modal/roofShape/passivity/Shed' import { useMessage } from '@/hooks/useMessage' +import { useRoofShapePassivitySetting } from '@/hooks/roofcover/useRoofShapePassivitySetting' export default function RoofShapePassivitySetting({ setShowRoofShapePassivitySettingModal }) { + const { handleSave, handleConfirm, handleRollback, buttons, type, setType, TYPES, offsetRef, pitchRef } = + useRoofShapePassivitySetting(setShowRoofShapePassivitySettingModal) const { getMessage } = useMessage() - const [buttonAct, setButtonAct] = useState(1) - const buttons = [ - { id: 1, name: getMessage('eaves') }, - { id: 2, name: getMessage('gable') }, - { id: 3, name: getMessage('windage') }, - ] + + const eavesProps = { + offsetRef, + pitchRef, + } + + const gableProps = { + offsetRef, + pitchRef, + } + + const shedProps = { + offsetRef, + } + return (
@@ -25,7 +36,7 @@ export default function RoofShapePassivitySetting({ setShowRoofShapePassivitySet
{buttons.map((button) => ( - ))} @@ -33,17 +44,23 @@ export default function RoofShapePassivitySetting({ setShowRoofShapePassivitySet
{getMessage('setting')}
- {buttonAct === 1 && } - {buttonAct === 2 && } - {buttonAct === 3 && } + {type === TYPES.EAVES && } + {type === TYPES.GABLE && } + {type === TYPES.SHED && }
- - + +
- +
diff --git a/src/components/floor-plan/modal/roofShape/RoofShapeSetting.jsx b/src/components/floor-plan/modal/roofShape/RoofShapeSetting.jsx index f906a67e..60355848 100644 --- a/src/components/floor-plan/modal/roofShape/RoofShapeSetting.jsx +++ b/src/components/floor-plan/modal/roofShape/RoofShapeSetting.jsx @@ -37,7 +37,7 @@ export default function RoofShapeSetting({ setShowRoofShapeSettingModal }) { buttonMenu, handleConfirm, handleRollBack, - } = useRoofShapeSetting() + } = useRoofShapeSetting(setShowRoofShapeSettingModal) const ridgeProps = { pitch, setPitch, eavesOffset, setEavesOffset } const patternProps = { pitch, setPitch, eavesOffset, setEavesOffset, gableOffset, setGableOffset } diff --git a/src/components/floor-plan/modal/roofShape/passivity/Eaves.jsx b/src/components/floor-plan/modal/roofShape/passivity/Eaves.jsx index 22e15a34..3a16b5c0 100644 --- a/src/components/floor-plan/modal/roofShape/passivity/Eaves.jsx +++ b/src/components/floor-plan/modal/roofShape/passivity/Eaves.jsx @@ -1,6 +1,6 @@ import { useMessage } from '@/hooks/useMessage' -export default function Eaves() { +export default function Eaves({ offsetRef, pitchRef }) { const { getMessage } = useMessage() return ( <> @@ -9,7 +9,7 @@ export default function Eaves() { {getMessage('slope')}
- +
@@ -18,7 +18,7 @@ export default function Eaves() { {getMessage('eaves.offset')}
- +
mm
diff --git a/src/components/floor-plan/modal/roofShape/passivity/Gable.jsx b/src/components/floor-plan/modal/roofShape/passivity/Gable.jsx index e2eb6342..69b2cf9d 100644 --- a/src/components/floor-plan/modal/roofShape/passivity/Gable.jsx +++ b/src/components/floor-plan/modal/roofShape/passivity/Gable.jsx @@ -1,6 +1,6 @@ import { useMessage } from '@/hooks/useMessage' -export default function Gable() { +export default function Gable({ offsetRef, pitchRef }) { const { getMessage } = useMessage() return ( <> @@ -9,7 +9,7 @@ export default function Gable() { {getMessage('slope')}
- +
@@ -18,7 +18,7 @@ export default function Gable() { {getMessage('gable.offset')}
- +
mm diff --git a/src/components/floor-plan/modal/roofShape/passivity/Shed.js b/src/components/floor-plan/modal/roofShape/passivity/Shed.js index dfdee92c..474c2f60 100644 --- a/src/components/floor-plan/modal/roofShape/passivity/Shed.js +++ b/src/components/floor-plan/modal/roofShape/passivity/Shed.js @@ -1,6 +1,6 @@ import { useMessage } from '@/hooks/useMessage' -export default function Shed() { +export default function Shed({ offsetRef }) { const { getMessage } = useMessage() return ( <> @@ -9,7 +9,7 @@ export default function Shed() { {getMessage('shed.width')}
- +
mm diff --git a/src/components/floor-plan/modal/wallLineOffset/WallLineOffsetSetting.jsx b/src/components/floor-plan/modal/wallLineOffset/WallLineOffsetSetting.jsx index fcd48af1..7e5230b1 100644 --- a/src/components/floor-plan/modal/wallLineOffset/WallLineOffsetSetting.jsx +++ b/src/components/floor-plan/modal/wallLineOffset/WallLineOffsetSetting.jsx @@ -7,9 +7,20 @@ import { useWallLineOffsetSetting } from '@/hooks/roofcover/useWallLineOffsetSet export default function WallLineOffsetSetting({ setShowWallLineOffsetSettingModal }) { const { getMessage } = useMessage() - const { type, setType, buttonMenu, currentWallLineRef, TYPES, radioTypeRef, arrow1Ref, arrow2Ref, length1Ref, length2Ref, handleSave } = - useWallLineOffsetSetting() - const [buttonAct, setButtonAct] = useState(1) + const { + type, + setType, + buttonMenu, + currentWallLineRef, + TYPES, + radioTypeRef, + arrow1Ref, + arrow2Ref, + length1Ref, + length2Ref, + handleSave, + wallLineEditRef, + } = useWallLineOffsetSetting(setShowWallLineOffsetSettingModal) const wallLineProps = { length1Ref, @@ -45,7 +56,7 @@ export default function WallLineOffsetSetting({ setShowWallLineOffsetSettingModa
{getMessage('setting')}
- {type === TYPES.WALL_LINE_EDIT && } + {type === TYPES.WALL_LINE_EDIT && } {type === TYPES.OFFSET && }
diff --git a/src/components/floor-plan/modal/wallLineOffset/type/WallLine.jsx b/src/components/floor-plan/modal/wallLineOffset/type/WallLine.jsx index 4f5c6ea7..15ccefa3 100644 --- a/src/components/floor-plan/modal/wallLineOffset/type/WallLine.jsx +++ b/src/components/floor-plan/modal/wallLineOffset/type/WallLine.jsx @@ -1,8 +1,8 @@ import { useMessage } from '@/hooks/useMessage' -import { useEffect, useState } from 'react' +import { forwardRef, useEffect, useImperativeHandle, useState } from 'react' import { useEvent } from '@/hooks/useEvent' -export default function WallLine({ length1Ref, length2Ref, arrow1Ref, arrow2Ref, radioTypeRef, currentWallLineRef }) { +export default forwardRef(function WallLine({ length1Ref, length2Ref, arrow1Ref, arrow2Ref, radioTypeRef, currentWallLineRef }, ref) { const { getMessage } = useMessage() const { addDocumentEventListener, initEvent } = useEvent() const [type, setType] = useState(1) @@ -10,76 +10,25 @@ export default function WallLine({ length1Ref, length2Ref, arrow1Ref, arrow2Ref, const [arrow2, setArrow2] = useState('up') useEffect(() => { - addDocumentEventListener('keydown', document, keyDown) - return () => { initEvent() } }, []) + useImperativeHandle(ref, () => ({ + setArrow, + })) + + const setArrow = () => { + setArrow1(arrow1Ref.current) + setArrow2(arrow2Ref.current) + } + const onChange = (e) => { setType(Number(e.target.value)) radioTypeRef.current = e.target.value } - const handleBtnClick = (direction) => { - document.dispatchEvent(new KeyboardEvent('keydown', { key: direction })) - } - - const keyDown = (e) => { - if (currentWallLineRef.current === null) { - alert('보조선을 먼저 선택하세요') - return - } - - const key = e.key - - switch (key) { - case 'Down': // IE/Edge에서 사용되는 값 - case 'ArrowDown': { - if (radioTypeRef.current === '1') { - setArrow1('down') - arrow1Ref.current = 'down' - } else { - setArrow2('down') - arrow2Ref.current = 'down' - } - - break - } - case 'Up': // IE/Edge에서 사용되는 값 - case 'ArrowUp': - if (radioTypeRef.current === '1') { - setArrow1('up') - arrow1Ref.current = 'up' - } else { - setArrow2('up') - arrow2Ref.current = 'up' - } - break - case 'Left': // IE/Edge에서 사용되는 값 - case 'ArrowLeft': - if (radioTypeRef.current === '1') { - setArrow1('left') - arrow1Ref.current = 'left' - } else { - setArrow2('left') - arrow2Ref.current = 'left' - } - break - case 'Right': // IE/Edge에서 사용되는 값 - case 'ArrowRight': - if (radioTypeRef.current === '1') { - setArrow1('right') - arrow1Ref.current = 'right' - } else { - setArrow2('right') - arrow2Ref.current = 'right' - } - break - } - } - return ( <>
@@ -107,10 +56,10 @@ export default function WallLine({ length1Ref, length2Ref, arrow1Ref, arrow2Ref,
- - - - + + + +
@@ -141,10 +90,10 @@ export default function WallLine({ length1Ref, length2Ref, arrow1Ref, arrow2Ref,
- - - - + + + +
@@ -155,4 +104,4 @@ export default function WallLine({ length1Ref, length2Ref, arrow1Ref, arrow2Ref,
) -} +}) diff --git a/src/components/header/Header.jsx b/src/components/header/Header.jsx index 65da6051..900a39c8 100644 --- a/src/components/header/Header.jsx +++ b/src/components/header/Header.jsx @@ -12,6 +12,10 @@ import { logout } from '@/lib/authActions' import QSelectBox from '@/components/common/select/QSelectBox' +import UserInfoModal from '@/components/myInfo/UserInfoModal' +import { useAxios } from '@/hooks/useAxios' +import { globalLocaleStore } from '@/store/localeAtom' + export const ToggleonMouse = (e, act, target) => { const listWrap = e.target.closest(target) const ListItem = Array.from(listWrap.childNodes) @@ -28,6 +32,8 @@ export const ToggleonMouse = (e, act, target) => { } export default function Header(props) { + const [userInfoModal, setUserInfoModal] = useState(false) + const { userSession } = props const [sessionState, setSessionState] = useRecoilState(sessionStore) const { getMessage } = useMessage() @@ -40,12 +46,42 @@ export default function Header(props) { const dimmedState = useRecoilValue(dimmedStore) const isDimmed = dimmedState ? 'opacity-50 bg-black' : '' - const SelectOptions = [ - { id: 0, name: 'オンライン保証シ', link: '' }, - { id: 1, name: 'ステム', link: '' }, - { id: 2, name: 'TEST1', link: 'https://www.weather.go.kr/w/index.do' }, - { id: 3, name: 'TEST2', link: 'https://www.google.com' }, - ] + // Link 이동 자동 로그인 + const [globalLocaleState, setGlbalLocaleState] = useRecoilState(globalLocaleStore) + const { promisePost } = useAxios(globalLocaleState) + + const qOrderUrl = process.env.NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL + const qMusubiUrl = process.env.NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL + + const [SelectOptions, setSelectOptions] = useState( + userSession.groupId === '60000' ? [{ id: 0, name: 'Q.ORDER', link: `${qOrderUrl}` }] : [{ id: 1, name: 'Q.Musubi', link: `${qMusubiUrl}` }], + ) + + const getAutoLoginParam = async () => { + await promisePost({ url: '/api/login/v1.0/user/login/autoLoginEncryptData', data: { loginId: userSession.userId } }) + .then((res) => { + if (res) { + setSelectOptions( + userSession.groupId === '60000' + ? [{ id: 0, name: 'Q.ORDER', link: `${qOrderUrl}?autoLoginParam1=${encodeURIComponent(res.data)}` }] + : [{ id: 1, name: 'Q.Musubi', link: `${qMusubiUrl}?autoLoginParam1=${encodeURIComponent(res.data)}` }], + ) + setSelected( + userSession.groupId === '60000' + ? { id: 0, name: 'Q.ORDER', link: `${qOrderUrl}?autoLoginParam1=${encodeURIComponent(res.data)}` } + : { id: 1, name: 'Q.Musubi', link: `${qMusubiUrl}?autoLoginParam1=${encodeURIComponent(res.data)}` }, + ) + } + }) + .catch((error) => { + alert(error.response.data.message) + }) + } + + useEffect(() => { + getAutoLoginParam() + }, [userSession]) + const menus = [ { id: 0, name: 'header.menus.home', url: '/', children: [] }, { @@ -96,7 +132,7 @@ export default function Header(props) { onMouseLeave={(e) => ToggleonMouse(e, 'remove', 'nav > ul')} > {menu.children.length === 0 ? ( - + {getMessage(menu.name)} ) : ( @@ -111,7 +147,9 @@ export default function Header(props) { onMouseEnter={(e) => ToggleonMouse(e, 'add', 'li > ul')} onMouseLeave={(e) => ToggleonMouse(e, 'remove', 'li > ul')} > - {getMessage(m.name)} + + {getMessage(m.name)} + ) })} @@ -137,9 +175,15 @@ export default function Header(props) {
- + { + setUserInfoModal(true) + }} + > + {userInfoModal && }
- +
- ) : ( <> + {!isFormValid ? ( + + ) : ( + + )} - )} )} + {showAddressButtonValid && } + {showDesignRequestButtonValid && ( + + )} + {showWindSpeedButtonValid && ( + + )} ) } diff --git a/src/components/management/StuffQGrid.jsx b/src/components/management/StuffQGrid.jsx index 4fd6c42d..33836e79 100644 --- a/src/components/management/StuffQGrid.jsx +++ b/src/components/management/StuffQGrid.jsx @@ -38,9 +38,8 @@ export default function StuffQGrid(props) { flex: 1, sortable: false, suppressMovable: true, - resizable: false, + resizable: true, suppressSizeToFit: false, - headerClass: 'centered', //_test.scss에 추가 테스트 } }, []) @@ -67,24 +66,7 @@ export default function StuffQGrid(props) { //더블클릭 const onCellDoubleClicked = useCallback((event) => { - // if (event.column.colId === 'company') { - // return - // } else { props.getCellDoubleClicked(event) - // } - }, []) - - //컨텐츠에 따라 컬럼넓이 자동조절 - const autoSizeStrategy = useMemo(() => { - return { - type: 'fitCellContents', - } - }, []) - - const onGridReady = useCallback((event) => { - // console.log('event:::', event) - // 헤더 사이즈 조정 컬럼에 width값으로 계산 - event.api.sizeColumnsToFit() }, []) // Fetch data & update rowData state @@ -92,11 +74,17 @@ export default function StuffQGrid(props) { gridData ? setRowData(gridData) : '' }, [gridData]) + // 임시는 row색깔 구분 + const getRowClass = (row) => { + if (row.data.objectNo.substring(0, 1) === 'T') { + return 'important_row' + } + } + return (
물건 목록이 없습니다.'} + getRowClass={getRowClass} + autoSizeAllColumns={true} />
) diff --git a/src/components/management/StuffSearchCondition.jsx b/src/components/management/StuffSearchCondition.jsx index 21b84dd8..b33608cf 100644 --- a/src/components/management/StuffSearchCondition.jsx +++ b/src/components/management/StuffSearchCondition.jsx @@ -4,7 +4,8 @@ import React, { useEffect, useRef, useState } from 'react' import { useAxios } from '@/hooks/useAxios' import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil' import { appMessageStore, globalLocaleStore } from '@/store/localeAtom' -import Select from 'react-dropdown-select' +// import Select from 'react-dropdown-select' +import Select from 'react-select' import KO from '@/locales/ko.json' import JA from '@/locales/ja.json' import { stuffSearchState } from '@/store/stuffAtom' @@ -42,7 +43,6 @@ export default function StuffSearchCondition() { const resetStuffRecoil = useResetRecoilState(stuffSearchState) const [stuffSearch, setStuffSearch] = useRecoilState(stuffSearchState) const [objectNo, setObjectNo] = useState('') //물건번호 - // const [saleStoreId, setSaleStoreId] = useState('') //판매대리점ID 세션에서 가져와서세팅 const [address, setAddress] = useState('') //물건주소 const [objectName, setobjectName] = useState('') //물건명 const [saleStoreName, setSaleStoreName] = useState('') //판매대리점명 @@ -74,13 +74,16 @@ export default function StuffSearchCondition() { startRow: stuffSearch?.startRow ? stuffSearch.startRow : 1, endRow: stuffSearch?.endRow ? stuffSearch.endRow : 100, schSortType: stuffSearch?.schSortType ? stuffSearch.schSortType : 'R', + selObject: { + label: stuffSearch.selObject.label, + value: stuffSearch.selObject.value, + }, }) } //초기화 const resetRecoil = () => { setObjectNo('') - //setSaleStoreId('') //세션정보 setAddress('') setobjectName('') setSaleStoreName('') @@ -96,12 +99,14 @@ export default function StuffSearchCondition() { useEffect(() => { if (isObjectNotEmpty(sessionState)) { - // console.log('판매대리점 리스트 가져오기 위한 세션정보::::::::', sessionState) // storeId가 T01 이거나 1차점일때만 판매대리점 선택 활성화 // get({ url: `/api/object/saleStore/201TES01/list` }).then((res) => { get({ url: `/api/object/saleStore/${sessionState?.storeId}/list` }).then((res) => { if (!isEmptyArray(res)) { - // console.log('판매점 결과:::::', res) + res.map((row) => { + row.value = row.saleStoreId + row.label = row.saleStoreName + }) setSchSelSaleStoreList(res) } }) @@ -110,18 +115,21 @@ export default function StuffSearchCondition() { //초기화 눌렀을 때 자동완성도.. const handleClear = () => { - // console.log('ref::', ref.current.state.values) - if (ref.current.state.dropDown) { - ref.current.methods.dropDown() - } else { - ref.current.state.values = [] + if (ref.current) { + ref.current.clearValue() } } + //판매대리점 자동완성 변경 const onSelectionChange = (key) => { - if (!isEmptyArray(key)) { - setSchSelSaleStoreId(key[0].saleStoreId) - setStuffSearch({ ...stuffSearch, code: 'S', schSelSaleStoreId: key[0].saleStoreId }) + if (isObjectNotEmpty(key)) { + setSchSelSaleStoreId(key.saleStoreId) + setStuffSearch({ + ...stuffSearch, + code: 'S', + schSelSaleStoreId: key.saleStoreId, + selObject: { value: key.saleStoreId, label: key.saleStoreName }, + }) } else { setSchSelSaleStoreId('') setStuffSearch({ ...stuffSearch, schSelSaleStoreId: '' }) @@ -147,19 +155,19 @@ export default function StuffSearchCondition() {
-

물건현황

+

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

- +
@@ -175,13 +183,12 @@ export default function StuffSearchCondition() { - 물건번호 + {getMessage('stuff.search.schObjectNo')}
{ setObjectNo(e.target.value) @@ -190,29 +197,26 @@ export default function StuffSearchCondition() { />
- 판매대리점명 + {getMessage('stuff.search.schSaleStoreName')}
{ - //setSaleStoreId(e.target.value) setSaleStoreName(e.target.value) setStuffSearch({ ...stuffSearch, code: 'S', schSaleStoreName: e.target.value }) }} />
- 물건주소 + {getMessage('stuff.search.schAddress')}
{ setAddress(e.target.value) @@ -223,13 +227,12 @@ export default function StuffSearchCondition() { - 물건명 + {getMessage('stuff.search.schObjectName')}
{ setobjectName(e.target.value) @@ -238,13 +241,12 @@ export default function StuffSearchCondition() { />
- 견적처 + {getMessage('stuff.search.schDispCompanyName')}
{ setDispCompanyName(e.target.value) @@ -253,31 +255,34 @@ export default function StuffSearchCondition() { />
- 판매대리점 선택 + {getMessage('stuff.search.schSelSaleStoreId')} - {schSelSaleStoreList?.length > 0 && ( - - )} +
+ {schSelSaleStoreList?.length > 0 && ( + { setReceiveUser(e.target.value) @@ -286,7 +291,7 @@ export default function StuffSearchCondition() { />
- 기간검색 + {getMessage('stuff.search.period')}
@@ -302,7 +307,7 @@ export default function StuffSearchCondition() { setStuffSearch({ ...stuffSearch, code: 'S', schDateType: e.target.value }) }} /> - +
- +
diff --git a/src/components/management/popup/FindAddressPop.jsx b/src/components/management/popup/FindAddressPop.jsx new file mode 100644 index 00000000..0c480a52 --- /dev/null +++ b/src/components/management/popup/FindAddressPop.jsx @@ -0,0 +1,165 @@ +import React, { useState } from 'react' +import { useForm } from 'react-hook-form' +import { queryStringFormatter } from '@/util/common-utils' +import { useAxios } from '@/hooks/useAxios' +import { globalLocaleStore } from '@/store/localeAtom' +import { useRecoilValue } from 'recoil' +import FindAddressPopQGrid from './FindAddressPopQGrid' +import { useMessage } from '@/hooks/useMessage' +import { isNotEmptyArray } from '@/util/common-utils' +export default function FindAddressPop(props) { + const globalLocaleState = useRecoilValue(globalLocaleStore) + + const { get } = useAxios(globalLocaleState) + + const { getMessage } = useMessage() + + const [prefId, setPrefId] = useState(null) + const [zipNo, setZipNo] = useState(null) + const [address1, setAddress1] = useState(null) + const [address2, setAddress2] = useState(null) + const [address3, setAddress3] = useState(null) + + const [gridProps, setGridProps] = useState({ + gridData: [], + isPageable: false, + gridColumns: [ + { + field: 'address1', + headerName: getMessage('stuff.addressPopup.gridHeader.address1'), + minWidth: 150, + checkboxSelection: true, + showDisabledCheckboxes: true, + }, + { + field: 'address2', + headerName: getMessage('stuff.addressPopup.gridHeader.address2'), + minWidth: 150, + }, + { + field: 'address3', + headerName: getMessage('stuff.addressPopup.gridHeader.address3'), + minWidth: 150, + }, + ], + }) + + //검색필드 + const formInitValue = { + zipNo: '', + } + const { register, setValue, getValues, handleSubmit, resetField, control, watch } = useForm({ + defaultValues: formInitValue, + }) + + const form = { register, setValue, getValues, handleSubmit, resetField, control, watch } + + //우편번호 검색 + const searchPostNum = () => { + // //7830060 + // //9302226 + // //0790177 3개짜리 + const params = { + zipcode: watch('zipNo'), + } + + get({ url: `https://zipcloud.ibsnet.co.jp/api/search?${queryStringFormatter(params)}` }).then((res) => { + if (res.status === 200) { + if (isNotEmptyArray(res.results)) { + setGridProps({ ...gridProps, gridData: res.results }) + } else { + alert(getMessage('stuff.addressPopup.error.message1')) + setGridProps({ ...gridProps, gridData: [] }) + } + } else { + alert(getMessage('stuff.addressPopup.error.message1')) + setGridProps({ ...gridProps, gridData: [] }) + } + }) + } + // 주소적용 클릭 + const applyAddress = () => { + if (prefId == null) { + alert(getMessage('stuff.addressPopup.error.message2')) + } else { + props.zipInfo({ + zipNo: zipNo, + address1: address1, + address2: address2, + address3: address3, + prefId: prefId, + }) + + //팝업닫기 + props.setShowAddressButtonValid(false) + } + } + + //숫자만 + const handleKeyUp = (e) => { + let input = e.target + input.value = input.value.replace(/[^0-9]/g, '') + + if (e.key === 'Enter') { + searchPostNum() + } + } + + //그리드에서 선택한 우편정보 + const getSelectedRowdata = (data) => { + if (isNotEmptyArray(data)) { + setAddress1(data[0].address1) + setAddress2(data[0].address2) + setAddress3(data[0].address3) + setPrefId(data[0].prefcode) + setZipNo(data[0].zipcode) + } else { + setAddress1(null) + setAddress2(null) + setAddress3(null) + setPrefId(null) + setZipNo(null) + } + } + + return ( +
+
+
+
+

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

+ +
+
+
+
+ + +
+
+ +
+
+
+ + +
+
+
+
+
+ ) +} diff --git a/src/components/management/popup/FindAddressPopQGrid.jsx b/src/components/management/popup/FindAddressPopQGrid.jsx new file mode 100644 index 00000000..b9d1bb90 --- /dev/null +++ b/src/components/management/popup/FindAddressPopQGrid.jsx @@ -0,0 +1,62 @@ +import React from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { AgGridReact } from 'ag-grid-react' + +import 'ag-grid-community/styles/ag-grid.css' +import 'ag-grid-community/styles/ag-theme-quartz.css' + +export default function FindAddressPopGrid(props) { + const { gridData, gridColumns, isPageable = true } = props + + const [rowData, setRowData] = useState(null) + + const [gridApi, setGridApi] = useState(null) + + const [colDefs, setColDefs] = useState(gridColumns) + + const defaultColDef = useMemo(() => { + return { + flex: 1, + minWidth: 100, + sortable: false, + suppressMovable: false, + resizable: false, + suppressSizeToFit: false, + } + }, []) + + const rowBuffer = 100 + + useEffect(() => { + gridData ? setRowData(gridData) : '' + }, [gridData]) + + const onGridReady = useCallback( + (params) => { + setGridApi(params.api) + gridData ? setRowData(gridData) : '' + }, + [gridData], + ) + + //부모로 선택한 우편정보 보내기 + const onSelectionChanged = () => { + const selectedData = gridApi.getSelectedRows() + props.getSelectedRowdata(selectedData) + } + + return ( +
+ +
+ ) +} diff --git a/src/components/management/popup/PlanRequestPop.jsx b/src/components/management/popup/PlanRequestPop.jsx new file mode 100644 index 00000000..fe6e2d2a --- /dev/null +++ b/src/components/management/popup/PlanRequestPop.jsx @@ -0,0 +1,449 @@ +import React, { useState, useRef, useEffect } from 'react' +import { useForm } from 'react-hook-form' +import { useAxios } from '@/hooks/useAxios' +import { globalLocaleStore } from '@/store/localeAtom' +import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil' +import FindAddressPopQGrid from './FindAddressPopQGrid' +import { useMessage } from '@/hooks/useMessage' +import { isNotEmptyArray } from '@/util/common-utils' +import SingleDatePicker from '@/components/common/datepicker/SingleDatePicker' +import dayjs from 'dayjs' +import PlanRequestPopQGrid from './PlanRequestPopQGrid' +import { sessionStore } from '@/store/commonAtom' +import { planReqSearchState } from '@/store/planReqAtom' +import { isObjectNotEmpty, queryStringFormatter } from '@/util/common-utils' + +import Select from 'react-select' +import QPagination from '@/components/common/pagination/QPagination' +export default function PlanRequestPop(props) { + const [pageNo, setPageNo] = useState(1) //현재 페이지 번호 + const [pageSize, setPageSize] = useState(20) //페이지 당 게시물 개수 + const [totalCount, setTotalCount] = useState(0) //총 갯수 + + const sessionState = useRecoilValue(sessionStore) + + const globalLocaleState = useRecoilValue(globalLocaleStore) + + const [planReqObject, setPlanReqObject] = useState({}) + + const { get, promiseGet } = useAxios(globalLocaleState) + + const { getMessage } = useMessage() + //Select ref + const ref = useRef() + // 검색조건 달력 셋팅 + const [startDate, setStartDate] = useState(dayjs(new Date()).add(-3, 'month').format('YYYY-MM-DD')) + const [endDate, setEndDate] = useState(dayjs(new Date()).format('YYYY-MM-DD')) + + const rangeDatePickerProps1 = { + startDate, //시작일 + setStartDate, + } + + const rangeDatePickerProps2 = { + startDate: endDate, //종료일 + setStartDate: setEndDate, + } + + const resetPlanReqRecoil = useResetRecoilState(planReqSearchState) + + const [planReqSearch, setPlanReqSearch] = useRecoilState(planReqSearchState) + const [schPlanReqNo, setSchPlanReqNo] = useState('') //설계의뢰번호 + const [schTitle, setSchTitle] = useState('') //안건명 + const [schAddress, setSchAddress] = useState('') //도도부현 + const [schSaleStoreName, setSchSaleStoreName] = useState('') //판매대리점명 + const [schPlanReqName, setSchPlanReqName] = useState('') //의뢰자명 + const [schPlanStatCd, setSchPlanStatCd] = useState('') //상태코드 + const [schDateGbn, setSchDateGbn] = useState('S') //기간구분코드(S/R) + + //초기화 + const resetRecoil = () => { + setSchPlanReqNo('') + setSchTitle('') + setSchAddress('') + setSchSaleStoreName('') + setSchPlanReqName('') + setSchDateGbn('S') + setStartDate(dayjs(new Date()).add(-3, 'month').format('YYYY-MM-DD')) + setEndDate(dayjs(new Date()).format('YYYY-MM-DD')) + setSchPlanStatCd('') + handleClear() //셀렉트 자동완성 초기화 + resetPlanReqRecoil() + } + + //셀렉트 자동완성 초기화 + const handleClear = () => { + if (ref.current) { + ref.current.clearValue() + } + } + + // 상태 검색조건 변경 + const onSelectionChange = (key) => { + //임시작업 + // console.log('E::::::::', key) + if (isObjectNotEmpty(key)) { + setSchPlanStatCd(key.value) + setPlanReqSearch({ + ...planReqSearch, + schPlanStatCd: key.value, + }) + } else { + //X누름 + setSchPlanStatCd('') + setPlanReqSearch({ ...planReqSearch, schPlanStatCd: '' }) + } + } + + useEffect(() => { + setStartDate(planReqSearch?.schStartDt ? planReqSearch.schStartDt : dayjs(new Date()).add(-3, 'month').format('YYYY-MM-DD')) + setEndDate(planReqSearch?.schEndDt ? planReqSearch.schEndDt : dayjs(new Date()).format('YYYY-MM-DD')) + }, [planReqSearch]) + + // 조회 + const onSubmit = (page, type) => { + const params = { + saleStoreId: 'T100', + saleStoreLevel: '1', + // saleStoreId: sessionState?.storeId, + // saleStoreLevel: sessionState?.storeLvl, + schPlanReqNo: planReqSearch?.schPlanReqNo ? planReqSearch.schPlanReqNo : schPlanReqNo, + schTitle: planReqSearch?.schTitle ? planReqSearch.schTitle : schTitle, + schAddress: planReqSearch?.schAddress ? planReqSearch.schAddress : schAddress, + schSaleStoreName: planReqSearch?.schSaleStoreName ? planReqSearch.schSaleStoreName : schSaleStoreName, + schPlanReqName: planReqSearch?.schPlanReqName ? planReqSearch.schPlanReqName : schPlanReqName, + schPlanStatCd: planReqSearch?.schPlanStatCd ? planReqSearch.schPlanStatCd : schPlanStatCd, + schDateGbn: planReqSearch?.schDateGbn ? planReqSearch.schDateGbn : schDateGbn, + // schStartDt: dayjs(startDate).format('YYYY-MM-DD'), + // schEndDt: dayjs(endDate).format('YYYY-MM-DD'), + startRow: type === 'S' ? (1 - 1) * pageSize + 1 : (page - 1) * pageSize + 1, + endRow: type === 'S' ? 1 * pageSize : page * pageSize, + } + if (type === 'S') { + setPageNo(1) + } else { + setPageNo(page) + } + + const apiUrl = `/api/object/planReq/list?${queryStringFormatter(params)}` + promiseGet({ url: apiUrl }).then((res) => { + if (res.status === 200) { + if (isNotEmptyArray(res.data.data)) { + setGridProps({ ...gridProps, gridData: res.data.data, gridCount: res.data.data[0].totCnt }) + setTotalCount(res.data.data[0].totCnt) + } else { + setGridProps({ ...gridProps, gridData: [], gridCount: 0 }) + setTotalCount(0) + } + } else { + setGridProps({ ...gridProps, gridData: [], gridCount: 0 }) + setTotalCount(0) + } + }) + } + // 페이징 현재페이지 변경 + const handleChangePage = (page) => { + setPlanReqSearch({ + ...planReqSearch, + startRow: (page - 1) * pageSize + 1, + endRow: page * pageSize, + }) + setPageNo(page) + + onSubmit(page, 'P') + } + + const [gridProps, setGridProps] = useState({ + gridData: [], + isPageable: false, + gridColumns: [ + { + field: 'planStatName', + headerName: getMessage('stuff.planReqPopup.gridHeader.planStatName'), + minWidth: 150, + checkboxSelection: true, + showDisabledCheckboxes: true, + }, + { + field: 'planReqNo', + headerName: getMessage('stuff.planReqPopup.gridHeader.planReqNo'), + minWidth: 150, + }, + { + field: 'saleStoreId', + headerName: getMessage('stuff.planReqPopup.gridHeader.saleStoreId'), + minWidth: 150, + }, + { + field: 'saleStoreName', + headerName: getMessage('stuff.planReqPopup.gridHeader.saleStoreName'), + minWidth: 150, + }, + { + field: 'title', + headerName: getMessage('stuff.planReqPopup.gridHeader.title'), + minWidth: 150, + }, + { + field: 'address1', + headerName: getMessage('stuff.planReqPopup.gridHeader.address1'), + minWidth: 150, + }, + { + field: 'houseCntName', + headerName: getMessage('stuff.planReqPopup.gridHeader.houseCntName'), + minWidth: 150, + }, + { + field: 'planReqName', + headerName: getMessage('stuff.planReqPopup.gridHeader.planReqName'), + minWidth: 150, + }, + { + field: 'submitDt', + headerName: getMessage('stuff.planReqPopup.gridHeader.submitDt'), + minWidth: 150, + }, + ], + }) + + //설계의뢰 그리드에서 선택한 설계의로 정보 + const getSelectedRowdata = (data) => { + if (isNotEmptyArray(data)) { + setPlanReqObject(data[0]) + } else { + setPlanReqObject({}) + } + } + + // 설계의뢰 적용 클릭 + const applyPlanReq = () => { + if (isObjectNotEmpty(planReqObject)) { + props.planReqInfo(planReqObject) + // 팝업닫기 + props.setShowDesignRequestButtonValid(false) + } else { + alert(getMessage('stuff.planReqPopup.error.message1')) + } + } + + const tempList = [ + { + label: '완료', + value: 'C', + }, + { + label: '저장', + value: 'I', + }, + { + label: '접수', + value: 'R', + }, + { + label: '제출', + value: 'S', + }, + ] + + return ( +
+
+
+
+

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

+ +
+
+
+
+

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

+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{getMessage('stuff.planReqPopup.search.planReqNo')} +
+ { + setSchPlanReqNo(e.target.value) + setPlanReqSearch({ ...planReqSearch, schPlanReqNo: e.target.value }) + }} + /> +
+
{getMessage('stuff.planReqPopup.search.title')} +
+ { + setSchTitle(e.target.value) + setPlanReqSearch({ ...planReqSearch, schTitle: e.target.value }) + }} + /> +
+
{getMessage('stuff.planReqPopup.search.address1')} +
+ { + setSchAddress(e.target.value) + setPlanReqSearch({ ...planReqSearch, schAddress: e.target.value }) + }} + /> +
+
{getMessage('stuff.planReqPopup.search.saleStoreName')} +
+ { + setSchSaleStoreName(e.target.value) + setPlanReqSearch({ ...planReqSearch, schSaleStoreName: e.target.value }) + }} + /> +
+
{getMessage('stuff.planReqPopup.search.planReqName')} +
+ { + setSchPlanReqName(e.target.value) + setPlanReqSearch({ ...planReqSearch, schPlanReqName: e.target.value }) + }} + /> +
+
{getMessage('stuff.planReqPopup.search.planStatName')} +
+
{getMessage('stuff.planReqPopup.search.period')} +
+
+
+ { + setSchDateGbn(e.target.value) + setPlanReqSearch({ ...planReqSearch, schDateGbn: e.target.value }) + }} + /> + +
+
+ { + setSchDateGbn(e.target.value) + setPlanReqSearch({ ...planReqSearch, schDateGbn: e.target.value }) + }} + /> + +
+
+
+
+ +
+ ~ +
+ +
+
+
+
+
+
+
+
Plan List
+ +
+ +
+
+
+
+ + +
+
+
+
+
+ ) +} diff --git a/src/components/management/popup/PlanRequestPopQGrid.jsx b/src/components/management/popup/PlanRequestPopQGrid.jsx new file mode 100644 index 00000000..e610a138 --- /dev/null +++ b/src/components/management/popup/PlanRequestPopQGrid.jsx @@ -0,0 +1,62 @@ +import React from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { AgGridReact } from 'ag-grid-react' + +import 'ag-grid-community/styles/ag-grid.css' +import 'ag-grid-community/styles/ag-theme-quartz.css' + +export default function PlanRequestPopQGrid(props) { + const { gridData, gridColumns, isPageable = true } = props + + const [rowData, setRowData] = useState(null) + + const [gridApi, setGridApi] = useState(null) + + const [colDefs, setColDefs] = useState(gridColumns) + + const defaultColDef = useMemo(() => { + return { + flex: 1, + minWidth: 100, + sortable: false, + suppressMovable: false, + resizable: false, + suppressSizeToFit: false, + } + }, []) + + const rowBuffer = 100 + + useEffect(() => { + gridData ? setRowData(gridData) : '' + }, [gridData]) + + const onGridReady = useCallback( + (params) => { + setGridApi(params.api) + gridData ? setRowData(gridData) : '' + }, + [gridData], + ) + + //부모로 선택한 설계의뢰정보 보내기 + const onSelectionChanged = () => { + const selectedData = gridApi.getSelectedRows() + props.getSelectedRowdata(selectedData) + } + + return ( +
+ +
+ ) +} diff --git a/src/components/management/popup/WindSelectPop.jsx b/src/components/management/popup/WindSelectPop.jsx new file mode 100644 index 00000000..db1795f8 --- /dev/null +++ b/src/components/management/popup/WindSelectPop.jsx @@ -0,0 +1,115 @@ +import React, { useState, useEffect } from 'react' +import { useMessage } from '@/hooks/useMessage' +import { useAxios } from '@/hooks/useAxios' +import { globalLocaleStore } from '@/store/localeAtom' +import { useRecoilValue } from 'recoil' +import { isEmptyArray, isNotEmptyArray } from '@/util/common-utils' +export default function WindSelectPop(props) { + const globalLocaleState = useRecoilValue(globalLocaleStore) + const { promiseGet } = useAxios(globalLocaleState) + const [windSpeedList, setWindSpeedList] = useState([]) + const [windSpeed, setWindSpeed] = useState(null) + const { getMessage } = useMessage() + + //선택한 라디오 값 세팅 + const handleChangeRadio = (e) => { + setWindSpeed(e.target.value) + } + + //적용 + const applyWindSpeed = () => { + if (windSpeed == null) { + alert(getMessage('stuff.windSelectPopup.error.message2')) + } else { + props.windSpeedInfo({ windSpeed: windSpeed }) + + //팝업닫기 + props.setShowWindSpeedButtonValid(false) + } + } + + useEffect(() => { + if (props.prefName !== '') { + promiseGet({ url: `/api/object/windSpeed/${props.prefName}/list` }).then((res) => { + if (res.status === 200) { + if (!isEmptyArray(res.data)) { + setWindSpeedList(res.data) + } + } + }) + } + }, [props]) + + return ( +
+
+
+
+

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

+ +
+
+
+
+ + + + + + + + + + + +
{getMessage('stuff.windSelectPopup.search.address1')} +
+ +
+
+
+
+ + + + + + + + + + {windSpeedList.map((row, index) => { + // console.log('row:::', row) + return ( + + + + + + ) + })} + +
{getMessage('stuff.windSelectPopup.table.selected')}{getMessage('stuff.windSelectPopup.table.windspeed')}Remarks
+
+ + +
+
{row.windSpeed}{row.remarks}
+
+
+
+ + +
+
+
+
+
+ ) +} diff --git a/src/components/myInfo/UserInfoModal.jsx b/src/components/myInfo/UserInfoModal.jsx new file mode 100644 index 00000000..7643913c --- /dev/null +++ b/src/components/myInfo/UserInfoModal.jsx @@ -0,0 +1,287 @@ +'use client' + +import { useState, useRef, useEffect } from 'react' +import { useAxios } from '@/hooks/useAxios' +import { useMessage } from '@/hooks/useMessage' + +export default function UserInfoModal({ userId, userInfoModal, setUserInfoModal }) { + const { getMessage } = useMessage() + + // api 조회 관련 + const { get, promisePatch, promisePost } = useAxios() + const [info, setInfo] = useState({ + userId: '', + name: '', + nameKana: '', + category: '', + tel: '', + fax: '', + mail: '', + groupId: '', + groupName: '', + }) + + const [password, setPassword] = useState('') + const [showPwd, setShowPwd] = useState(false) + const [chkChgPwd, setChkChgPwd] = useState(false) + const [chgPwd, setChgPwd] = useState('') + const pwdInput = useRef() + const chgPwdInput = useRef() + + useEffect(() => { + async function fetchData() { + const url = `/api/my-info/my-profile` + const params = new URLSearchParams({ userId: userId }) + + const apiUrl = `${url}?${params.toString()}` + + const resultData = await get({ url: apiUrl }) + + if (resultData) { + setInfo(resultData) + } else { + alert(getMessage('common.message.no.data')) + } + } + + if (userId) { + fetchData() + } + }, []) + + const pattern = /^[\u0020-\u007E]{1,10}$/ + + // 비밀번호 변경 + const handleChangePassword = async () => { + if (chgPwd === '') { + chgPwdInput.current.focus() + return alert(getMessage('myinfo.message.validation.password4')) + } + if (password === chgPwd) { + chgPwdInput.current.focus() + return alert(getMessage('myinfo.message.validation.password2')) + } + if (!pattern.test(chgPwd)) { + chgPwdInput.current.focus() + return alert(getMessage('myinfo.message.validation.password3')) + } + + const params = { loginId: info.userId, chgType: 'C', pwd: password, chgPwd: chgPwd } + + await promisePatch({ url: '/api/login/v1.0/user/change-password', data: params }) + .then((res) => { + if (res) { + if (res.data.result.resultCode === 'S') { + alert(getMessage('myinfo.message.save')) + setChkChgPwd(false) + setPassword(chgPwd) + setChgPwd('') + } else { + alert(res.data.result.resultMsg) + } + } + }) + .catch((error) => { + alert(error.response.data.message) + }) + } + + // 비밀번호 변경 버튼 클릭 시, + const checkPasswordProcess = async (e) => { + if (password === '') { + pwdInput.current.focus() + return alert(getMessage('myinfo.message.validation.password1')) + } + + const param = { + loginId: userId, + pwd: password, + } + await promisePost({ url: '/api/login/v1.0/login', data: param }) + .then((res) => { + if (res) { + if (res.data.result.resultCode === 'S') { + setChkChgPwd(true) + setTimeout(() => { + chgPwdInput.current.focus() + }, 10) + } else { + alert(getMessage('myinfo.message.password.error')) + setChkChgPwd(false) + setChgPwd('') + setTimeout(() => { + pwdInput.current.focus() + }, 10) + } + } + }) + .catch((error) => { + alert(error.response.data.message) + }) + } + + return ( + <> +
+
+
+
+

{getMessage('myinfo.title')}

+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{getMessage('myinfo.info.userId')} +
+ +
+
{getMessage('myinfo.info.nameKana')} +
+ +
+
{getMessage('myinfo.info.name')} +
+ +
+
{getMessage('myinfo.info.password')} +
+
+ { + setPassword(e.target.value) + }} + /> + +
+ {getMessage('myinfo.sub.validation.password')} + +
+
+ {getMessage('myinfo.info.chg.password')} * + +
+
+ { + setChgPwd(e.target.value) + }} + /> +
+ + +
+
{getMessage('myinfo.info.category')} +
+ +
+
{getMessage('myinfo.info.tel')} +
+ +
+
{getMessage('myinfo.info.fax')} +
+ +
+
{getMessage('myinfo.info.mail')} +
+ +
+
+
+
+
+ +
+
+
+
+
+ + ) +} diff --git a/src/hooks/object/useObjectBatch.js b/src/hooks/object/useObjectBatch.js new file mode 100644 index 00000000..7229f470 --- /dev/null +++ b/src/hooks/object/useObjectBatch.js @@ -0,0 +1,415 @@ +'use client' +import { useMessage } from '@/hooks/useMessage' +import { useRecoilState, useRecoilValue } from 'recoil' +import { canvasState } from '@/store/canvasAtom' +import { INPUT_TYPE, BATCH_TYPE } from '@/common/common' +import { useEvent } from '@/hooks/useEvent' +import { + polygonToTurfPolygon, + rectToPolygon, + triangleToPolygon, + pointsToTurfPolygon, + splitDormerTriangle, + setSurfaceShapePattern, +} from '@/util/canvas-util' +import { useSwal } from '@/hooks/useSwal' +import * as turf from '@turf/turf' +import { QPolygon } from '@/components/fabric/QPolygon' +import { drawDirectionArrow } from '@/util/qpolygon-utils' + +export function useObjectBatch() { + const { getMessage } = useMessage() + const canvas = useRecoilValue(canvasState) + const { addCanvasMouseEventListener, initEvent } = useEvent() + const { swalFire } = useSwal() + + const applyOpeningAndShadow = (objectPlacement, buttonAct, surfaceShapePolygons) => { + const selectedType = objectPlacement.typeRef.current.find((radio) => radio.checked).value + const isCrossChecked = buttonAct === 1 ? objectPlacement.isCrossRef.current.checked : false + const objName = buttonAct === 1 ? BATCH_TYPE.OPENING : BATCH_TYPE.SHADOW + const objTempName = buttonAct === 1 ? BATCH_TYPE.OPENING_TEMP : BATCH_TYPE.SHADOW_TEMP + + let rect, isDown, origX, origY + let selectedSurface + + //프리입력 + if (selectedType === INPUT_TYPE.FREE) { + addCanvasMouseEventListener('mouse:down', (e) => { + isDown = true + const pointer = canvas.getPointer(e.e) + + surfaceShapePolygons.forEach((surface) => { + if (surface.inPolygon({ x: pointer.x, y: pointer.y })) { + selectedSurface = surface + } + }) + + if (!selectedSurface) { + swalFire({ text: '지붕안에 그려야해요', icon: 'error' }) + initEvent() //이벤트 초기화 + return + } + + origX = pointer.x + origY = pointer.y + + rect = new fabric.Rect({ + left: origX, + top: origY, + originX: 'left', + originY: 'top', + width: 0, + height: 0, + angle: 0, + stroke: 'black', + }) + + //개구냐 그림자냐에 따라 변경 + rect.set({ + fill: buttonAct === 1 ? 'white' : 'rgba(0, 0, 0, 0.4)', + name: objTempName, + }) + + canvas?.add(rect) + }) + + addCanvasMouseEventListener('mouse:move', (e) => { + if (!isDown) return + + if (selectedSurface) { + const pointer = canvas.getPointer(e.e) + const width = pointer.x - origX + const height = pointer.y - origY + + rect.set({ width: Math.abs(width), height: Math.abs(height) }) + + if (width < 0) { + rect.set({ left: Math.abs(pointer.x) }) + } + if (height < 0) { + rect.set({ top: Math.abs(pointer.y) }) + } + + canvas?.renderAll() + } + }) + + addCanvasMouseEventListener('mouse:up', (e) => { + if (rect) { + const rectPolygon = pointsToTurfPolygon(rectToPolygon(rect)) + const selectedSurfacePolygon = polygonToTurfPolygon(selectedSurface) + + //지붕 밖으로 그렸을때 + if (!turf.booleanWithin(rectPolygon, selectedSurfacePolygon)) { + swalFire({ text: '개구를 배치할 수 없습니다.', icon: 'error' }) + //일단 지워 + deleteTempObjects() + return + } + + if (!isCrossChecked) { + const preObjects = canvas?.getObjects().filter((obj) => obj.name === BATCH_TYPE.OPENING || obj.name === BATCH_TYPE.SHADOW) + const preObjectsArray = preObjects.map((obj) => rectToPolygon(obj)) + const isCross = preObjectsArray.some((object) => turf.booleanOverlap(pointsToTurfPolygon(object), rectPolygon)) + + if (isCross) { + swalFire({ text: '겹치기 불가요...', icon: 'error' }) + deleteTempObjects() + return + } + } + + isDown = false + rect.set({ name: objName }) + rect.setCoords() + initEvent() + } + }) + } else if (selectedType === INPUT_TYPE.DIMENSION) { + const width = objectPlacement.widthRef.current.value / 10 + const height = objectPlacement.heightRef.current.value / 10 + + if (width === '' || height === '' || width <= 0 || height <= 0) { + swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' }) + return + } + + // setShowObjectSettingModal(false) //메뉴보이고 + addCanvasMouseEventListener('mouse:move', (e) => { + isDown = true + if (!isDown) return + + canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === objTempName)) //움직일때 일단 지워가면서 움직임 + const pointer = canvas.getPointer(e.e) + + surfaceShapePolygons.forEach((surface) => { + if (surface.inPolygon({ x: pointer.x, y: pointer.y })) { + selectedSurface = surface + } + }) + + rect = new fabric.Rect({ + fill: 'white', + stroke: 'black', + strokeWidth: 1, + width: width, + height: height, + left: pointer.x - width / 2, + top: pointer.y - height / 2, + selectable: true, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + }) + + //개구냐 그림자냐에 따라 변경 + rect.set({ + fill: buttonAct === 1 ? 'white' : 'rgba(0, 0, 0, 0.4)', + name: objTempName, + }) + + canvas?.add(rect) + }) + + addCanvasMouseEventListener('mouse:up', (e) => { + if (rect) { + const rectPolygon = pointsToTurfPolygon(rectToPolygon(rect)) + const selectedSurfacePolygon = polygonToTurfPolygon(selectedSurface) + + //지붕 밖으로 그렸을때 + if (!turf.booleanWithin(rectPolygon, selectedSurfacePolygon)) { + swalFire({ text: '개구를 배치할 수 없습니다.', icon: 'error' }) + //일단 지워 + deleteTempObjects() + return + } + + if (!isCrossChecked) { + const preObjects = canvas?.getObjects().filter((obj) => obj.name === BATCH_TYPE.OPENING || obj.name === BATCH_TYPE.SHADOW) + const preObjectsArray = preObjects.map((obj) => rectToPolygon(obj)) + const isCross = preObjectsArray.some((object) => turf.booleanOverlap(pointsToTurfPolygon(object), rectPolygon)) + + if (isCross) { + swalFire({ text: '겹치기 불가요...', icon: 'error' }) + deleteTempObjects() + return + } + } + + isDown = false + rect.set({ name: objName }) + rect.setCoords() + initEvent() + } + }) + } + } + + const applyDormers = (dormerPlacement, buttonAct, surfaceShapePolygons) => { + const dormerName = buttonAct === 3 ? BATCH_TYPE.TRIANGLE_DORMER : BATCH_TYPE.PENTAGON_DORMER + const dormerTempName = buttonAct === 3 ? BATCH_TYPE.TRIANGLE_DORMER_TEMP : BATCH_TYPE.PENTAGON_DORMER_TEMP + const height = dormerPlacement.heightRef.current.value / 10 + const pitch = dormerPlacement.pitchRef.current.value + const directionRef = dormerPlacement.directionRef.current + const offsetRef = dormerPlacement.offsetRef.current.value === '' ? 0 : parseInt(dormerPlacement.offsetRef.current.value) / 10 + + let dormer, dormerOffset, isDown, selectedSurface + + console.log('dormerPlacement', dormerPlacement) + + if (height === '' || pitch === '' || height <= 0 || pitch <= 0) { + swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' }) + return + } + + //삼각형 도머 + if (buttonAct === 3) { + const bottomLength = height / (pitch * 0.25) + const bottomOffsetLength = (height + offsetRef) / (pitch * 0.25) + + console.log(bottomOffsetLength) + + addCanvasMouseEventListener('mouse:move', (e) => { + isDown = true + if (!isDown) return + + canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === dormerTempName)) //움직일때 일단 지워가면서 움직임 + const pointer = canvas.getPointer(e.e) + + surfaceShapePolygons.forEach((surface) => { + if (surface.inPolygon({ x: pointer.x, y: pointer.y })) { + selectedSurface = surface + } + }) + + let angle = 0 + if (directionRef === 'left') { + //서 + angle = 90 + } else if (directionRef === 'right') { + //동 + angle = 270 + } else if (directionRef === 'up') { + //북 + angle = 180 + } + + dormer = new fabric.Triangle({ + fill: 'white', + stroke: 'red', + strokeDashArray: [5, 5], + strokeWidth: 1, + width: bottomLength * 2, + height: height, + left: pointer.x, + top: pointer.y, + selectable: true, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + name: dormerTempName, + originX: 'center', + originY: 'top', + angle: angle, + }) + canvas?.add(dormer) + + if (offsetRef > 0) { + dormerOffset = new fabric.Triangle({ + fill: 'gray', + stroke: 'red', + strokeDashArray: [5, 5], + strokeWidth: 1, + width: bottomOffsetLength * 2, + height: height + offsetRef, + left: pointer.x, + top: pointer.y, + selectable: true, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + name: dormerTempName, + originX: 'center', + originY: 'top', + angle: angle, + }) + canvas?.add(dormerOffset) + } + }) + + addCanvasMouseEventListener('mouse:up', (e) => { + if (dormer) { + // const trianglePolygon = pointsToTurfPolygon(triangleToPolygon(dormer)) + // const selectedSurfacePolygon = polygonToTurfPolygon(selectedSurface) + + // //지붕 밖으로 그렸을때 + // if (!turf.booleanWithin(trianglePolygon, selectedSurfacePolygon)) { + // swalFire({ text: '개구를 배치할 수 없습니다.', icon: 'error' }) + // //일단 지워 + // deleteTempObjects() + // return + // } + + //각도 추가 + let originAngle = 0 //기본 남쪽 + let direction = 'south' + + if (directionRef === 'left') { + //서 + originAngle = 90 + direction = 'west' + } else if (directionRef === 'right') { + //동 + originAngle = 270 + direction = 'east' + } else if (directionRef === 'up') { + //북 + originAngle = 180 + direction = 'north' + } + + let splitedTriangle = offsetRef > 0 ? splitDormerTriangle(dormerOffset, directionRef) : splitDormerTriangle(dormer, directionRef) + canvas?.remove(offsetRef > 0 ? dormerOffset : dormer) + + if (offsetRef > 0) + dormer.set({ + name: dormerName, + stroke: 'black', + strokeWidth: 1, + strokeDashArray: [0], + }) //오프셋이 있을땐 같이 도머로 만든다 + + const leftTriangle = new QPolygon(splitedTriangle[0], { + fill: 'transparent', + stroke: 'black', + strokeWidth: 1, + selectable: true, + lockMovementX: true, // X 축 이동 잠금 + lockMovementY: true, // Y 축 이동 잠금 + lockRotation: true, // 회전 잠금 + viewLengthText: true, + fontSize: 14, + direction: direction, + originX: 'center', + originY: 'center', + name: dormerName, + }) + + const rightTriangle = new QPolygon(splitedTriangle[1], { + fill: 'transparent', + stroke: 'black', + strokeWidth: 1, + selectable: true, + lockMovementX: true, // X 축 이동 잠금 + lockMovementY: true, // Y 축 이동 잠금 + lockRotation: true, // 회전 잠금 + viewLengthText: true, + fontSize: 14, + direction: direction, + originX: 'center', + originY: 'center', + name: dormerName, + }) + + canvas?.add(leftTriangle) + canvas?.add(rightTriangle) + + //패턴 + setSurfaceShapePattern(leftTriangle) + setSurfaceShapePattern(rightTriangle) + //방향 + drawDirectionArrow(leftTriangle) + drawDirectionArrow(rightTriangle) + + isDown = false + initEvent() + } + }) + } + } + + const deleteTempObjects = () => { + const deleteTarget = canvas + ?.getObjects() + .filter( + (obj) => + obj.name === BATCH_TYPE.OPENING_TEMP || + obj.name === BATCH_TYPE.SHADOW_TEMP || + obj.name === BATCH_TYPE.TRIANGLE_DORMER_TEMP || + obj.name === BATCH_TYPE.PENTAGON_DORMER_TEMP, + ) + canvas?.remove(...deleteTarget) + initEvent() //이벤트 초기화 + } + + return { + applyOpeningAndShadow, + applyDormers, + } +} diff --git a/src/hooks/roofcover/useAuxiliaryDrawing.js b/src/hooks/roofcover/useAuxiliaryDrawing.js index de213685..f479a92e 100644 --- a/src/hooks/roofcover/useAuxiliaryDrawing.js +++ b/src/hooks/roofcover/useAuxiliaryDrawing.js @@ -23,31 +23,23 @@ import { outerLineLength2State, outerLineTypeState, } from '@/store/outerLineAtom' -import { calculateDistance, calculateIntersection, distanceBetweenPoints, findClosestPoint } from '@/util/canvas-util' +import { calculateDistance, calculateIntersection, distanceBetweenPoints, findClosestPoint, polygonToTurfPolygon } from '@/util/canvas-util' import { fabric } from 'fabric' import { useAdsorptionPoint } from '@/hooks/useAdsorptionPoint' +import { useSwal } from '@/hooks/useSwal' +import { booleanPointInPolygon } from '@turf/turf' -export function useAuxiliaryDrawing() { +// 보조선 작성 +export function useAuxiliaryDrawing(setShowAuxiliaryModal) { const canvas = useRecoilValue(canvasState) - const { - addCanvasMouseEventListener, - addDocumentEventListener, - removeAllMouseEventListeners, - removeAllDocumentEventListeners, - removeMouseEvent, - removeMouseLine, - initEvent, - } = useEvent() + const { addCanvasMouseEventListener, addDocumentEventListener, removeMouseLine, initEvent } = useEvent() const { getIntersectMousePoint } = useMouse() const { addLine, removeLine } = useLine() const { tempGridMode } = useTempGrid() + const { swalFire } = useSwal() const { getAdsorptionPoints } = useAdsorptionPoint() - const verticalHorizontalMode = useRecoilValue(verticalHorizontalModeState) - const adsorptionPointAddMode = useRecoilValue(adsorptionPointAddModeState) - const adsorptionPointMode = useRecoilValue(adsorptionPointModeState) const adsorptionRange = useRecoilValue(adsorptionRangeState) - const interval = useRecoilValue(dotLineIntervalSelector) // 가로 세로 간격 const [buttonAct, setButtonAct] = useState(1) @@ -85,12 +77,16 @@ export function useAuxiliaryDrawing() { useEffect(() => { typeRef.current = type + clear() + addDocumentEventListener('keydown', document, keydown[type]) }, [type]) useEffect(() => { // innerLines가 있을경우 삭제 const roofs = canvas?.getObjects().filter((obj) => obj.name === 'roofBase') if (roofs.length === 0) { + swalFire({ text: '지붕형상이 없습니다.' }) + setShowAuxiliaryModal(false) return } @@ -101,7 +97,7 @@ export function useAuxiliaryDrawing() { addCanvasMouseEventListener('mouse:move', mouseMove) addCanvasMouseEventListener('mouse:down', mouseDown) addDocumentEventListener('contextmenu', document, cutAuxiliary) - addDocumentEventListener('keydown', document, keydown) + addDocumentEventListener('keydown', document, keydown[type]) return () => { canvas.remove(...canvas.getObjects().filter((obj) => obj.name === 'innerPoint')) @@ -110,12 +106,8 @@ export function useAuxiliaryDrawing() { } }, []) - useEffect(() => { - clear() - addDocumentEventListener('keydown', document, keydown[type]) - }, [type]) - const clear = () => { + addCanvasMouseEventListener('mouse:move', mouseMove) setLength1(0) setLength2(0) @@ -130,7 +122,6 @@ export function useAuxiliaryDrawing() { const keydown = { outerLine: (e) => { - console.log(123) if (mousePointerArr.current.length === 0) { return } @@ -467,7 +458,9 @@ export function useAuxiliaryDrawing() { } const mouseDown = (e) => { + canvas.renderAll() const pointer = getIntersectMousePoint(e) + console.log(pointer) mousePointerArr.current.push(pointer) if (mousePointerArr.current.length === 2) { @@ -498,7 +491,6 @@ export function useAuxiliaryDrawing() { auxiliaryLines.forEach((line1) => { auxiliaryLines.forEach((line2) => { - const lines = [line1, line2] if (line1 === line2) { return } @@ -546,59 +538,66 @@ export function useAuxiliaryDrawing() { // 보조선 절삭 const cutAuxiliary = (e) => { - const auxiliaryLines = canvas.getObjects().filter((obj) => obj.name === 'auxiliaryLine' && !obj.isFixed) + const auxiliaryLines = canvas.getObjects().filter((obj) => obj.name === 'auxiliaryLine') if (auxiliaryLines.length === 0) { return } + const roofBases = canvas.getObjects().filter((obj) => obj.name === 'roofBase') + + const allLines = [...auxiliaryLines] + + roofBases.forEach((roofBase) => { + roofBase.lines.forEach((line) => { + allLines.push(line) + }) + }) + auxiliaryLines.forEach((line1) => { - auxiliaryLines.forEach((line2) => { - const lines = [line1, line2] + allLines.forEach((line2) => { if (line1 === line2) { return } - const intersectionPoint = calculateIntersection(line1, line2) - if (!intersectionPoint || intersectionPoints.current.some((point) => point.x === intersectionPoint.x && point.y === intersectionPoint.y)) { + if (!intersectionPoint) { return } roofAdsorptionPoints.current.push(intersectionPoint) intersectionPoints.current.push(intersectionPoint) - lines.forEach((line) => { - const distance1 = distanceBetweenPoints({ x: line.x1, y: line.y1 }, intersectionPoint) - const distance2 = distanceBetweenPoints({ x: line.x2, y: line.y2 }, intersectionPoint) - if (distance1 === 0 || distance2 === 0) { - return - } - //historyLine에서 기존 line을 제거한다. - lineHistory.current = lineHistory.current.filter((history) => history !== line) + const distance1 = distanceBetweenPoints({ x: line1.x1, y: line1.y1 }, intersectionPoint) + const distance2 = distanceBetweenPoints({ x: line1.x2, y: line1.y2 }, intersectionPoint) - let newLine + if (distance1 === 0 || distance2 === 0) { + return + } + //historyLine에서 기존 line을 제거한다. + lineHistory.current = lineHistory.current.filter((history) => history !== line1) - if (distance1 >= distance2) { - newLine = addLine([line.x1, line.y1, intersectionPoint.x, intersectionPoint.y], { - stroke: 'black', - strokeWidth: 1, - selectable: false, - name: 'auxiliaryLine', - isFixed: true, - intersectionPoint, - }) - } else { - newLine = addLine([line.x2, line.y2, intersectionPoint.x, intersectionPoint.y], { - stroke: 'black', - strokeWidth: 1, - selectable: false, - name: 'auxiliaryLine', - isFixed: true, - intersectionPoint, - }) - } - lineHistory.current.push(newLine) - removeLine(line) - }) + let newLine + + if (distance1 >= distance2) { + newLine = addLine([line1.x1, line1.y1, intersectionPoint.x, intersectionPoint.y], { + stroke: 'black', + strokeWidth: 1, + selectable: false, + name: 'auxiliaryLine', + isFixed: true, + intersectionPoint, + }) + } else { + newLine = addLine([line1.x2, line1.y2, intersectionPoint.x, intersectionPoint.y], { + stroke: 'black', + strokeWidth: 1, + selectable: false, + name: 'auxiliaryLine', + isFixed: true, + intersectionPoint, + }) + } + lineHistory.current.push(newLine) + removeLine(line1) }) }) addCanvasMouseEventListener('mouse:move', mouseMove) @@ -624,11 +623,31 @@ export function useAuxiliaryDrawing() { addCanvasMouseEventListener('mouse:move', mouseMove) } - const handleFix = (fn) => { + const handleFix = () => { if (!confirm('지붕선 완료하시겠습니까?')) { return } - fn(close) + + const roofBases = canvas.getObjects().filter((obj) => obj.name === 'roofBase') + const innerLines = [...lineHistory.current] + + roofBases.forEach((roofBase) => { + const roofInnerLines = innerLines.filter((line) => { + const turfPolygon = polygonToTurfPolygon(roofBase) + + // innerLines의 두 점이 모두 polygon 안에 있는지 확인 + const inPolygon1 = booleanPointInPolygon([line.x1, line.y1], turfPolygon) + const inPolygon2 = booleanPointInPolygon([line.x2, line.y2], turfPolygon) + + if (inPolygon1 && inPolygon2) { + return true + } + }) + + roofBase.innerLines = [...roofInnerLines] + }) + + setShowAuxiliaryModal(false) } return { diff --git a/src/hooks/roofcover/useEavesGableEdit.js b/src/hooks/roofcover/useEavesGableEdit.js index d2c69303..083d8e03 100644 --- a/src/hooks/roofcover/useEavesGableEdit.js +++ b/src/hooks/roofcover/useEavesGableEdit.js @@ -6,8 +6,11 @@ import { useEvent } from '@/hooks/useEvent' import { LINE_TYPE } from '@/common/common' import { useLine } from '@/hooks/useLine' import { useMode } from '@/hooks/useMode' +import { outerLineFixState } from '@/store/outerLineAtom' +import { useSwal } from '@/hooks/useSwal' -export function useEavesGableEdit() { +// 처마.케라바 변경 +export function useEavesGableEdit(setShowEavesGableEditModal) { const canvas = useRecoilValue(canvasState) const { getMessage } = useMessage() const { addCanvasMouseEventListener, initEvent } = useEvent() @@ -20,6 +23,7 @@ export function useEavesGableEdit() { const [type, setType] = useState(TYPES.EAVES) const typeRef = useRef(TYPES.EAVES) const { removeLine } = useLine() + const { swalFire } = useSwal() const { drawRoofPolygon } = useMode() @@ -27,6 +31,7 @@ export function useEavesGableEdit() { const offsetRef = useRef(null) const widthRef = useRef(null) const radioTypeRef = useRef('1') // 각 페이지에서 사용하는 radio type + const outerLineFix = useRecoilValue(outerLineFixState) const buttonMenu = [ { id: 1, name: getMessage('eaves'), type: TYPES.EAVES }, @@ -35,6 +40,14 @@ export function useEavesGableEdit() { { id: 4, name: getMessage('shed'), type: TYPES.SHED }, ] + useEffect(() => { + const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + if (!outerLineFix || outerLines.length === 0) { + swalFire({ text: '외벽선이 없습니다.' }) + setShowEavesGableEditModal(false) + } + }, []) + useEffect(() => { const wallLines = canvas.getObjects().filter((obj) => obj.name === 'wallLine') wallLines.forEach((wallLine) => { diff --git a/src/hooks/roofcover/useOuterLineWall.js b/src/hooks/roofcover/useOuterLineWall.js index e37495d9..00405a3b 100644 --- a/src/hooks/roofcover/useOuterLineWall.js +++ b/src/hooks/roofcover/useOuterLineWall.js @@ -29,8 +29,10 @@ import { } from '@/store/outerLineAtom' import { calculateAngle } from '@/util/qpolygon-utils' import { fabric } from 'fabric' +import { QLine } from '@/components/fabric/QLine' -export function useOuterLineWall() { +//외벽선 그리기 +export function useOuterLineWall(setShowOutlineModal) { const canvas = useRecoilValue(canvasState) const { addCanvasMouseEventListener, addDocumentEventListener, removeAllMouseEventListeners, removeAllDocumentEventListeners, removeMouseEvent } = useEvent() @@ -67,8 +69,6 @@ export function useOuterLineWall() { const isFix = useRef(false) - const closeModalFn = useRef(null) - useEffect(() => { if (adsorptionPointAddMode || tempGridMode) { return @@ -208,18 +208,19 @@ export function useOuterLineWall() { removeAllMouseEventListeners() removeAllDocumentEventListeners() canvas?.renderAll() - closeModalFn.current(false) + setOuterLineFix(true) + setShowOutlineModal(false) } if (points.length < 3) { return } - /*if (lastPoint.x === firstPoint.x && lastPoint.y === firstPoint.y) { + if (Math.abs(lastPoint.x - firstPoint.x) < 1 && Math.abs(lastPoint.y - firstPoint.y) < 1) { return } - if (lastPoint.x === firstPoint.x || lastPoint.y === firstPoint.y) { + if (Math.abs(lastPoint.x - firstPoint.x) < 1 || Math.abs(lastPoint.y - firstPoint.y) < 1) { let isAllRightAngle = true const firstPoint = points[0] @@ -238,38 +239,27 @@ export function useOuterLineWall() { if (isAllRightAngle) { return } - const line = new QLine([lastPoint.x, lastPoint.y, firstPoint.x, firstPoint.y], { + const line = addLine([lastPoint.x, lastPoint.y, firstPoint.x, firstPoint.y], { stroke: 'grey', strokeWidth: 1, selectable: false, name: 'helpGuideLine', }) - - canvas?.add(line) - addLineText(line) } else { - const guideLine1 = new QLine([lastPoint.x, lastPoint.y, lastPoint.x, firstPoint.y], { + const guideLine1 = addLine([lastPoint.x, lastPoint.y, lastPoint.x, firstPoint.y], { stroke: 'grey', strokeWidth: 1, strokeDashArray: [1, 1, 1], name: 'helpGuideLine', }) - const guideLine2 = new QLine([guideLine1.x2, guideLine1.y2, firstPoint.x, firstPoint.y], { + const guideLine2 = addLine([guideLine1.x2, guideLine1.y2, firstPoint.x, firstPoint.y], { stroke: 'grey', strokeWidth: 1, strokeDashArray: [1, 1, 1], name: 'helpGuideLine', }) - if (guideLine1.length > 0) { - canvas?.add(guideLine1) - addLineText(guideLine1) - } - - canvas?.add(guideLine2) - - addLineText(guideLine2) - }*/ + } } }, [points]) @@ -748,7 +738,7 @@ export function useOuterLineWall() { setPoints((prev) => prev.slice(0, prev.length - 1)) } - const handleFix = (fn) => { + const handleFix = () => { if (points.length < 3) { return } @@ -778,7 +768,6 @@ export function useOuterLineWall() { }) isFix.current = true - closeModalFn.current = fn } return { diff --git a/src/hooks/roofcover/usePropertiesSetting.js b/src/hooks/roofcover/usePropertiesSetting.js index ebd5182e..ad703356 100644 --- a/src/hooks/roofcover/usePropertiesSetting.js +++ b/src/hooks/roofcover/usePropertiesSetting.js @@ -7,7 +7,8 @@ import { usePolygon } from '@/hooks/usePolygon' import { useLine } from '@/hooks/useLine' import { outerLinePointsState } from '@/store/outerLineAtom' -export function usePropertiesSetting() { +// 외벽선 속성 설정 +export function usePropertiesSetting(setShowPropertiesSettingModal) { const canvas = useRecoilValue(canvasState) const currentObject = useRecoilValue(currentObjectState) @@ -19,17 +20,7 @@ export function usePropertiesSetting() { const { removeLine, hideLine } = useLine() useEffect(() => { - if (!currentObject) { - return - } - if (currentObject.name !== 'outerLine') { - return - } - - const type = currentObject.attributes?.type - const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') - lines.forEach((line) => { const lineType = line.attributes?.type if (!lineType) { @@ -39,6 +30,14 @@ export function usePropertiesSetting() { }) } }) + if (!currentObject) { + return + } + if (currentObject.name !== 'outerLine') { + return + } + + const type = currentObject.attributes?.type if (!type) { currentObject.set({ @@ -46,6 +45,8 @@ export function usePropertiesSetting() { strokeWidth: 4, }) } + + canvas.renderAll() }, [currentObject]) const history = useRef([]) @@ -106,6 +107,7 @@ export function usePropertiesSetting() { } const lastLine = history.current.pop() + delete lastLine.attributes lastLine.set({ stroke: '#000000', strokeWidth: 4, @@ -131,7 +133,7 @@ export function usePropertiesSetting() { hideLine(line) }) - const wall = addPolygonByLines(lines, { name: 'WallLine', fill: 'transparent', stroke: 'black' }) + const wall = addPolygonByLines(lines, { name: 'wallLine', fill: 'transparent', stroke: 'black' }) wall.lines = [...lines] @@ -159,7 +161,7 @@ export function usePropertiesSetting() { canvas.renderAll() setPoints([]) - fn(false) + setShowPropertiesSettingModal(false) } return { handleSetEaves, handleSetGable, handleRollback, handleFix, closeModal } diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js new file mode 100644 index 00000000..bc55c921 --- /dev/null +++ b/src/hooks/roofcover/useRoofAllocationSetting.js @@ -0,0 +1,143 @@ +import { useRecoilValue } from 'recoil' +import { canvasState } from '@/store/canvasAtom' +import { useEffect, useState } from 'react' +import { setSurfaceShapePattern } from '@/util/canvas-util' +import { drawDirectionArrow, splitPolygonWithLines } from '@/util/qpolygon-utils' +import { useSwal } from '@/hooks/useSwal' + +// 지붕면 할당 +export function useRoofAllocationSetting(setShowRoofAllocationSettingModal) { + const canvas = useRecoilValue(canvasState) + + const { swalFire } = useSwal() + + 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 [selectedRoofMaterial, setSelectedRoofMaterial] = useState(roofMaterials[0]) + + useEffect(() => { + const roofBases = canvas.getObjects().filter((obj) => obj.name === 'roofBase') + if (roofBases.length === 0) { + swalFire({ text: '할당할 지붕이 없습니다.' }) + setShowRoofAllocationSettingModal(false) + } + }, []) + + const onAddRoofMaterial = () => { + setValues([...values, selectedRoofMaterial]) + } + + const onDeleteRoofMaterial = (id) => { + setValues(values.filter((value) => value.id !== id)) + } + + // 선택한 지붕재로 할당 + const handleSave = () => { + const roofBases = canvas.getObjects().filter((obj) => obj.name === 'roofBase') + const wallLines = canvas.getObjects().filter((obj) => obj.name === 'wallLine') + roofBases.forEach((roofBase) => { + try { + splitPolygonWithLines(roofBase) + } catch (e) { + return + } + + roofBase.innerLines.forEach((line) => { + canvas.remove(line) + }) + + canvas.remove(roofBase) + }) + + wallLines.forEach((wallLine) => { + canvas.remove(wallLine) + }) + + const roofs = canvas.getObjects().filter((obj) => obj.name === 'roof') + + roofs.forEach((roof) => { + setSurfaceShapePattern(roof) + drawDirectionArrow(roof) + }) + setShowRoofAllocationSettingModal(false) + } + + const handleRadioOnChange = (e) => { + setRadioValue(e.target) + } + + return { + handleSave, + onAddRoofMaterial, + onDeleteRoofMaterial, + handleRadioOnChange, + widths, + lengths, + rafters, + values, + roofMaterials, + selectedRoofMaterial, + setSelectedRoofMaterial, + radioValue, + setRadioValue, + } +} diff --git a/src/hooks/roofcover/useRoofShapePassivitySetting.js b/src/hooks/roofcover/useRoofShapePassivitySetting.js new file mode 100644 index 00000000..bedbcd11 --- /dev/null +++ b/src/hooks/roofcover/useRoofShapePassivitySetting.js @@ -0,0 +1,220 @@ +import { canvasState, currentObjectState } from '@/store/canvasAtom' +import { useRecoilValue } from 'recoil' +import { useEffect, useRef, useState } from 'react' +import { useLine } from '@/hooks/useLine' +import { useMessage } from '@/hooks/useMessage' +import { useEvent } from '@/hooks/useEvent' +import { LINE_TYPE } from '@/common/common' +import { useMode } from '@/hooks/useMode' +import { usePolygon } from '@/hooks/usePolygon' +import { outerLineFixState } from '@/store/outerLineAtom' +import { useSwal } from '@/hooks/useSwal' + +//지붕형상 수동 설정 +export function useRoofShapePassivitySetting(setShowRoofShapePassivitySettingModal) { + const TYPES = { + EAVES: 'eaves', + GABLE: 'gable', + SHED: 'shed', + } + const canvas = useRecoilValue(canvasState) + const { getMessage } = useMessage() + const { showLine, hideLine } = useLine() + const { swalFire } = useSwal() + const { addCanvasMouseEventListener, initEvent } = useEvent() + const { drawRoofPolygon } = useMode() + const { addPolygonByLines } = usePolygon() + const currentObject = useRecoilValue(currentObjectState) + const offsetRef = useRef(null) + const pitchRef = useRef(null) + const currentLineRef = useRef(null) + + const history = useRef([]) + + const [type, setType] = useState(TYPES.EAVES) + + const isFix = useRef(false) + const initLines = useRef([]) + + const [isLoading, setIsLoading] = useState(false) + + const buttons = [ + { id: 1, name: getMessage('eaves'), type: TYPES.EAVES }, + { id: 2, name: getMessage('gable'), type: TYPES.GABLE }, + { id: 3, name: getMessage('windage'), type: TYPES.SHED }, + ] + + const outerLineFix = useRecoilValue(outerLineFixState) + + useEffect(() => { + const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + if (!outerLineFix || outerLines.length === 0) { + swalFire({ text: '외벽선이 없습니다.' }) + setShowRoofShapePassivitySettingModal(false) + return + } + setIsLoading(true) + }, []) + + useEffect(() => { + if (!isLoading) return + addCanvasMouseEventListener('mouse:down', mouseDown) + const wallLines = canvas.getObjects().filter((obj) => obj.name === 'wallLine') + + canvas?.remove(...wallLines) + + const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + initLines.current = outerLines.map((line) => ({ ...line })) + outerLines.forEach((outerLine, idx) => { + if (idx === 0) { + currentLineRef.current = outerLine + } + outerLine.set({ selectable: true }) + showLine(outerLine) + outerLine.bringToFront() + }) + canvas?.renderAll() + + return () => { + handleLineToPolygon() + canvas?.discardActiveObject() + initEvent() + } + }, [isLoading]) + + useEffect(() => { + const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + lines.forEach((line) => { + line.set({ + stroke: '#000000', + strokeWidth: 4, + }) + }) + if (!currentObject) { + return + } + if (currentObject.name !== 'outerLine') { + return + } + + currentObject.set({ + stroke: '#EA10AC', + strokeWidth: 4, + }) + + currentLineRef.current = currentObject + + canvas.renderAll() + }, [currentObject]) + + const mouseDown = (e) => { + if (!e.target) { + currentLineRef.current = null + return + } + + if (e.target && e.target.name === 'outerLine') { + currentLineRef.current = e.target + } + } + + const nextLineFocus = (selectedLine) => { + const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + const index = lines.findIndex((line) => line === selectedLine) + + const nextLine = lines[index + 1] || lines[0] + canvas.setActiveObject(nextLine) + } + + const handleConfirm = () => { + if (!currentLineRef.current) { + alert('선택된 외곽선이 없습니다.') + return + } + let attributes + const offset = Number(offsetRef.current.value) / 10 + if (type === TYPES.EAVES) { + attributes = { + type: LINE_TYPE.WALLLINE.EAVES, + offset, + pitch: pitchRef.current.value, + } + } else if (type === TYPES.GABLE) { + attributes = { + type: LINE_TYPE.WALLLINE.GABLE, + pitch: pitchRef.current.value, + offset, + } + } else if (type === TYPES.SHED) { + attributes = { + type: LINE_TYPE.WALLLINE.SHED, + offset, + } + } + + currentLineRef.current.set({ + attributes, + }) + + history.current.push(currentLineRef.current) + nextLineFocus(currentLineRef.current) + } + + const handleRollback = () => { + if (history.current.length === 0) { + return + } + const lastLine = history.current.pop() + + delete lastLine.attributes + lastLine.set({ + stroke: '#000000', + strokeWidth: 4, + }) + + canvas.setActiveObject(lastLine) + canvas.renderAll() + } + + const handleSave = () => { + isFix.current = true + handleLineToPolygon() + + setShowRoofShapePassivitySettingModal(false) + } + + const handleLineToPolygon = () => { + const roofBases = canvas.getObjects().filter((obj) => obj.name === 'roofBase') + const exceptObjs = canvas.getObjects().filter((obj) => obj.name !== 'outerLine' && obj.parent?.name !== 'outerLine') + const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + exceptObjs.forEach((obj) => { + canvas.remove(obj) + }) + + lines.forEach((line) => { + hideLine(line) + }) + + let wall + + if (isFix.current) { + wall = addPolygonByLines(lines, { name: 'wallLine', fill: 'transparent', stroke: 'black' }) + } else { + // 그냥 닫을 경우 처리 + wall = addPolygonByLines([...initLines.current], { name: 'wallLine', fill: 'transparent', stroke: 'black' }) + lines.forEach((line, idx) => { + line.attributes = initLines.current[idx].attributes + }) + } + + wall.lines = [...lines] + // 기존 그려진 지붕이 없다면 + if (roofBases.length === 0) { + return + } + const roof = drawRoofPolygon(wall) + canvas.renderAll() + } + + return { handleSave, handleConfirm, buttons, type, setType, TYPES, offsetRef, pitchRef, handleRollback } +} diff --git a/src/hooks/roofcover/useRoofShapeSetting.js b/src/hooks/roofcover/useRoofShapeSetting.js index 2c51b2bd..016cd365 100644 --- a/src/hooks/roofcover/useRoofShapeSetting.js +++ b/src/hooks/roofcover/useRoofShapeSetting.js @@ -6,10 +6,14 @@ import { LINE_TYPE, MENU } from '@/common/common' import { usePolygon } from '@/hooks/usePolygon' import { useMode } from '@/hooks/useMode' import { useLine } from '@/hooks/useLine' +import { outerLineFixState } from '@/store/outerLineAtom' +import { useSwal } from '@/hooks/useSwal' -export function useRoofShapeSetting() { +// 지붕형상 설정 +export function useRoofShapeSetting(setShowRoofShapeSettingModal) { const [shapeNum, setShapeNum] = useState(1) const [buttonAct, setButtonAct] = useState(1) + const { swalFire } = useSwal() const { getMessage } = useMessage() const canvas = useRecoilValue(canvasState) const { addPolygonByLines } = usePolygon() @@ -27,9 +31,18 @@ export function useRoofShapeSetting() { const { hideLine, showLine } = useLine() const setCurrentMenu = useSetRecoilState(currentMenuState) + const outerLineFix = useRecoilValue(outerLineFixState) const history = useRef([]) + useEffect(() => { + const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + if (!outerLineFix || outerLines.length === 0) { + swalFire({ text: '외벽선이 없습니다.' }) + setShowRoofShapeSettingModal(false) + } + }, []) + useEffect(() => { if (shapeNum !== 4) { return @@ -98,11 +111,7 @@ export function useRoofShapeSetting() { { id: 6, name: getMessage('shed') }, ] - /** - * - * @param fn 모달 닫기 위한 함수 - */ - const handleSave = (fn) => { + const handleSave = () => { //기존 wallLine 삭제 let outerLines @@ -235,6 +244,14 @@ export function useRoofShapeSetting() { } } + // 기존 wallLine, roofBase 제거 + canvas + .getObjects() + .filter((obj) => obj.name === 'wallLine' || obj.name === 'roofBase') + .forEach((line) => { + canvas.remove(line) + }) + const polygon = addPolygonByLines(outerLines, { name: 'wallLine' }) polygon.lines = [...outerLines] @@ -242,7 +259,7 @@ export function useRoofShapeSetting() { canvas?.renderAll() roof.drawHelpLine() - fn && fn(false) + setShowRoofShapeSettingModal(false) } const initLineSetting = () => { @@ -387,7 +404,8 @@ export function useRoofShapeSetting() { // 벽 attributes = { type: LINE_TYPE.WALLLINE.WALL, - offset: hasSleeve === '0' ? 0 : sleeveOffset / 10, + width: hasSleeve === '0' ? 0 : sleeveOffset / 10, + sleeve: hasSleeve === '1', } break } @@ -415,7 +433,7 @@ export function useRoofShapeSetting() { // 한쪽흐름 attributes = { type: LINE_TYPE.WALLLINE.SHED, - offset: shedWidth / 10, + width: shedWidth / 10, } break } diff --git a/src/hooks/roofcover/useWallLineOffsetSetting.js b/src/hooks/roofcover/useWallLineOffsetSetting.js index 6536bc80..6f65b58e 100644 --- a/src/hooks/roofcover/useWallLineOffsetSetting.js +++ b/src/hooks/roofcover/useWallLineOffsetSetting.js @@ -4,12 +4,16 @@ import { useEffect, useRef, useState } from 'react' import { useMessage } from '@/hooks/useMessage' import { useEvent } from '@/hooks/useEvent' import { useLine } from '@/hooks/useLine' +import { useSwal } from '@/hooks/useSwal' -export function useWallLineOffsetSetting() { +// 외벽선 편집 및 오프셋 +export function useWallLineOffsetSetting(setShowWallLineOffsetSettingModal) { const canvas = useRecoilValue(canvasState) const { showLine, addLine } = useLine() const { getMessage } = useMessage() + const { swalFire } = useSwal() const { addCanvasMouseEventListener, initEvent } = useEvent() + const wallLineEditRef = useRef(null) const length1Ref = useRef(null) const length2Ref = useRef(null) const radioTypeRef = useRef('1') @@ -17,6 +21,25 @@ export function useWallLineOffsetSetting() { const arrow1Ref = useRef(null) const arrow2Ref = useRef(null) + const [isLoading, setIsLoading] = useState(false) + + const drawLine = (point1, point2, idx, direction = currentWallLineRef.current.direction) => { + const line = addLine([point1.x, point1.y, point2.x, point2.y], { + stroke: 'black', + strokeWidth: 4, + idx: idx, + selectable: true, + name: 'outerLine', + x1: point1.x, + y1: point1.y, + x2: point2.x, + y2: point2.y, + direction: direction, + }) + + line.attributes = { ...currentWallLineRef.current.attributes } + } + const TYPES = { WALL_LINE_EDIT: 'wallLineEdit', OFFSET: 'offset', @@ -31,42 +54,128 @@ export function useWallLineOffsetSetting() { useEffect(() => { const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + if (outerLines.length === 0) { + swalFire({ text: '외벽선이 없습니다.' }) + setShowWallLineOffsetSettingModal(false) + return + } outerLines.forEach((outerLine) => { outerLine.set({ selectable: true }) showLine(outerLine) }) - const roofs = canvas.getObjects().filter((obj) => obj.name === 'roofBase') - - roofs.forEach((roof) => { - roof.innerLines.forEach((innerLine) => { - canvas.remove(innerLine) - }) - canvas.remove(roof) + //outerLine과 그 text만 남겨둔다. + const exceptObjs = canvas.getObjects().filter((obj) => obj.name !== 'outerLine' && obj.parent?.name !== 'outerLine') + exceptObjs.forEach((obj) => { + canvas.remove(obj) }) - const wallLines = canvas.getObjects().filter((obj) => obj.name === 'wallLine') - wallLines.forEach((wallLine) => { - canvas.remove(wallLine) - }) + canvas.setActiveObject(outerLines[0]) + currentWallLineRef.current = outerLines[0] + addCircleByLine(currentWallLineRef.current) addCanvasMouseEventListener('mouse:down', mouseDown) - + setIsLoading(true) return () => { + removeOuterLineEditCircle() canvas.discardActiveObject() initEvent() } }, []) + useEffect(() => { + if (!isLoading) { + return + } + removeOuterLineEditCircle() + addCanvasMouseEventListener('mouse:down', mouseDown) + if (type === TYPES.WALL_LINE_EDIT) { + addCircleByLine(currentWallLineRef.current) + } + }, [type]) + + const removeOuterLineEditCircle = () => { + canvas.remove(...canvas.getObjects().filter((obj) => obj.name === 'outerLineEditCircleStart' || obj.name === 'outerLineEditCircleEnd')) + } + const mouseDown = (e) => { + removeOuterLineEditCircle() if (!e.target || (e.target && e.target.name !== 'outerLine')) { + currentWallLineRef.current = null return } currentWallLineRef.current = e.target + console.log(currentWallLineRef.current.idx, currentWallLineRef.current.direction) + if (type === TYPES.WALL_LINE_EDIT) { + addCircleByLine(currentWallLineRef.current) + } + } + + const addCircleByLine = (line) => { + const direction = currentWallLineRef.current?.direction + let startPoint + let endPoint + let circle1, circle2 + + if (direction === 'top' || direction === 'bottom') { + // y1, y2중에 더 작은 값이 startPoint, 더 큰 값이 endPoint + if (line.y1 > line.y2) { + startPoint = { x: line.x2, y: line.y2 } + endPoint = { x: line.x1, y: line.y1 } + } else { + startPoint = { x: line.x1, y: line.y1 } + endPoint = { x: line.x2, y: line.y2 } + } + arrow1Ref.current = 'down' + arrow2Ref.current = 'up' + } else { + // x1, x2중에 더 작은 값이 startPoint, 더 큰 값이 endPoint + if (line.x1 > line.x2) { + startPoint = { x: line.x2, y: line.y2 } + endPoint = { x: line.x1, y: line.y1 } + } else { + startPoint = { x: line.x1, y: line.y1 } + endPoint = { x: line.x2, y: line.y2 } + } + arrow1Ref.current = 'right' + arrow2Ref.current = 'left' + } + + circle1 = new fabric.Circle({ + radius: 10, + fill: 'red', + top: startPoint.y - 10, + left: startPoint.x - 10, + name: 'outerLineEditCircleStart', + hasControls: false, + hasBorders: false, + lockMovementX: true, + lockMovementY: true, + }) + + circle2 = new fabric.Circle({ + radius: 10, + fill: 'blue', + top: endPoint.y - 10, + left: endPoint.x - 10, + name: 'outerLineEditCircleEnd', + hasControls: false, + hasBorders: false, + lockMovementX: true, + lockMovementY: true, + }) + + wallLineEditRef.current.setArrow() + canvas.add(circle1) + canvas.add(circle2) } const handleSave = () => { + if (currentWallLineRef.current === null) { + alert('보조선을 먼저 선택하세요') + return + } switch (type) { case TYPES.WALL_LINE_EDIT: handleWallLineEditSave() @@ -78,51 +187,324 @@ export function useWallLineOffsetSetting() { } const handleWallLineEditSave = () => { - const direction = currentWallLineRef.current.direction - let canDirections = direction === 'left' || direction === 'right' ? ['up', 'down'] : ['left', 'right'] - if (radioTypeRef === 1) { - if (!canDirections.includes(arrow1Ref.current)) { - alert('방향을 다시 선택하세요') - return + const startPoint = canvas + .getObjects() + .filter((obj) => obj.name === 'outerLineEditCircleStart') + .map((obj) => { + return { + x: obj.left + obj.radius, + y: obj.top + obj.radius, + } + })[0] + const endPoint = canvas + .getObjects() + .filter((obj) => obj.name === 'outerLineEditCircleEnd') + .map((obj) => { + return { + x: obj.left + obj.radius, + y: obj.top + obj.radius, + } + })[0] + const length = Number(length1Ref.current.value) / 10 + const length2 = Number(length2Ref.current.value) / 10 + const currentIdx = currentWallLineRef.current.idx + const currentDirection = currentWallLineRef.current.direction + let point1, point2, point3 + if (radioTypeRef.current === '1') { + // 1지점 선택시 방향은 down, right만 가능 + if (arrow1Ref.current === 'down') { + if (currentDirection === 'top') { + point1 = { x: endPoint.x, y: endPoint.y } + point2 = { x: startPoint.x, y: startPoint.y + length } + point3 = { x: startPoint.x, y: startPoint.y } + } else { + point1 = { x: startPoint.x, y: startPoint.y } + point2 = { x: startPoint.x, y: startPoint.y + length } + point3 = { x: endPoint.x, y: endPoint.y } + } + } else { + if (currentDirection === 'left') { + point1 = { x: endPoint.x, y: endPoint.y } + point2 = { x: startPoint.x + length, y: startPoint.y } + point3 = { x: startPoint.x, y: startPoint.y } + } else { + point1 = { x: startPoint.x, y: startPoint.y } + point2 = { x: startPoint.x + length, y: startPoint.y } + point3 = { x: endPoint.x, y: endPoint.y } + } } } else { - if (!canDirections.includes(arrow2Ref.current)) { - alert('방향을 다시 선택하세요') - return + // 2지점일 경우 방향은 up, left만 가능 + if (arrow2Ref.current === 'up') { + if (currentDirection === 'bottom') { + point1 = { x: startPoint.x, y: startPoint.y } + point2 = { x: endPoint.x, y: endPoint.y - length2 } + point3 = { x: endPoint.x, y: endPoint.y } + } else { + point1 = { x: endPoint.x, y: endPoint.y } + point2 = { x: endPoint.x, y: endPoint.y - length2 } + point3 = { x: startPoint.x, y: startPoint.y } + } + } else { + if (currentDirection === 'right') { + point1 = { x: startPoint.x, y: startPoint.y } + point2 = { x: endPoint.x - length2, y: endPoint.y } + point3 = { x: endPoint.x, y: endPoint.y } + } else { + point1 = { x: endPoint.x, y: endPoint.y } + point2 = { x: endPoint.x - length2, y: endPoint.y } + point3 = { x: startPoint.x, y: startPoint.y } + } } } + + rearrangeOuterLine(currentIdx + 1) + + drawLine(point1, point2, currentIdx) + drawLine(point2, point3, currentIdx + 1) + + removeOuterLineEditCircle() + canvas.remove(currentWallLineRef.current) + currentWallLineRef.current = null + canvas.renderAll() + } + + const rearrangeOuterLine = (idxParam) => { + const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + outerLines.forEach((outerLine) => { + if (outerLine.idx >= idxParam) { + outerLine.idx = outerLine.idx + 1 + } + }) } const handleOffsetSave = () => { const direction = currentWallLineRef.current.direction let canDirections = direction === 'left' || direction === 'right' ? ['up', 'down'] : ['left', 'right'] - + const currentIdx = currentWallLineRef.current.idx if (!canDirections.includes(arrow1Ref.current)) { alert('방향을 다시 선택하세요') return } const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + const idx = currentWallLineRef.current.idx - const prevLine = outerLines.find((line) => (line.idx === idx - 1 < 0 ? outerLines.length - 1 : idx - 1)) + const prevIdx = idx - 1 <= 0 ? outerLines.length : idx - 1 + const nextIdx = idx + 1 > outerLines.length ? 1 : idx + 1 + const currentLine = currentWallLineRef.current - const nextLine = outerLines.find((line) => (line.idx === idx + 1 > outerLines.length - 1 ? 0 : idx + 1)) + const prevLine = outerLines.find((line) => line.idx === prevIdx) + const nextLine = outerLines.find((line) => line.idx === nextIdx) + + const length = length1Ref.current.value / 10 + const currentLineX = Math.floor(Math.max(currentLine.x1, currentLine.x2)) + const currentLineY = Math.floor(Math.max(currentLine.y1, currentLine.y2)) switch (arrow1Ref.current) { case 'up': { - console.log(prevLine, currentLine, nextLine) + if (prevLine.direction === currentLine.direction) { + const newX = + currentLine.direction === 'left' + ? Math.floor(Math.max(currentLine.x1, currentLine.x2)) + : Math.floor(Math.min(currentLine.x1, currentLine.x2)) + + const newPoint1 = { x: newX, y: currentLineY - length } + const newPoint2 = { x: prevLine.x2, y: prevLine.y2 } + rearrangeOuterLine(currentIdx) + drawLine(newPoint1, newPoint2, currentIdx, 'top') + + if (Math.abs(currentLineY - nextLine.y1) < 2) { + nextLine.set({ y1: currentLineY - length }) + } else { + nextLine.set({ y2: currentLineY - length }) + } + } else if (nextLine.direction === currentLine.direction) { + const newX = + currentLine.direction === 'left' + ? Math.floor(Math.min(currentLine.x1, currentLine.x2)) + : Math.floor(Math.max(currentLine.x1, currentLine.x2)) + + const newPoint1 = { x: newX, y: currentLineY - length } + const newPoint2 = { x: nextLine.x1, y: nextLine.y1 } + rearrangeOuterLine(currentIdx + 1) + drawLine(newPoint1, newPoint2, currentIdx + 1, 'top') + + if (Math.abs(currentLineY - prevLine.y1) < 2) { + prevLine.set({ y1: currentLineY - length }) + } else { + prevLine.set({ y2: currentLineY - length }) + } + } else { + if (Math.abs(currentLineY - prevLine.y1) < 2) { + prevLine.set({ y1: prevLine.y1 - length }) + } else { + prevLine.set({ y2: prevLine.y2 - length }) + } + if (Math.abs(currentLineY - nextLine.y1) < 2) { + nextLine.set({ y1: nextLine.y1 - length }) + } else { + nextLine.set({ y2: nextLine.y2 - length }) + } + } + + currentLine.set({ y1: currentLine.y1 - length, y2: currentLine.y2 - length }) + break } case 'down': { + if (prevLine.direction === currentLine.direction) { + const newX = + currentLine.direction === 'left' + ? Math.floor(Math.max(currentLine.x1, currentLine.x2)) + : Math.floor(Math.min(currentLine.x1, currentLine.x2)) + const newPoint1 = { x: newX, y: currentLineY + length } + const newPoint2 = { x: prevLine.x2, y: prevLine.y2 } + rearrangeOuterLine(currentIdx) + drawLine(newPoint1, newPoint2, currentIdx, 'bottom') + if (Math.abs(currentLineY - nextLine.y1) < 2) { + nextLine.set({ y1: currentLineY + length }) + } else { + nextLine.set({ y2: currentLineY + length }) + } + } else if (nextLine.direction === currentLine.direction) { + const newX = + currentLine.direction === 'left' + ? Math.floor(Math.min(currentLine.x1, currentLine.x2)) + : Math.floor(Math.max(currentLine.x1, currentLine.x2)) + const newPoint1 = { x: newX, y: currentLineY + length } + const newPoint2 = { x: nextLine.x1, y: nextLine.y1 } + rearrangeOuterLine(currentIdx + 1) + drawLine(newPoint1, newPoint2, currentIdx + 1, 'bottom') + if (Math.abs(currentLineY - prevLine.y1) < 2) { + prevLine.set({ y1: currentLineY + length }) + } else { + prevLine.set({ y2: currentLineY + length }) + } + } else { + if (Math.abs(currentLineY - prevLine.y1) < 2) { + prevLine.set({ y1: prevLine.y1 + length }) + } else { + prevLine.set({ y2: prevLine.y2 + length }) + } + if (Math.abs(currentLineY - nextLine.y1) < 2) { + nextLine.set({ y1: nextLine.y1 + length }) + } else { + nextLine.set({ y2: nextLine.y2 + length }) + } + } + + currentLine.set({ y1: currentLine.y1 + length, y2: currentLine.y2 + length }) break } case 'left': { + if (prevLine.direction === currentLine.direction) { + const newY = + currentLine.direction === 'top' + ? Math.floor(Math.max(currentLine.y1, currentLine.y2)) + : Math.floor(Math.min(currentLine.y1, currentLine.y2)) + const newPoint1 = { x: currentLineX - length, y: newY } + const newPoint2 = { x: prevLine.x2, y: prevLine.y2 } + rearrangeOuterLine(currentIdx) + drawLine(newPoint1, newPoint2, currentIdx, 'left') + if (Math.abs(currentLineX - nextLine.x1) < 2) { + nextLine.set({ x1: currentLineX - length }) + } else { + nextLine.set({ x2: currentLineX - length }) + } + } else if (nextLine.direction === currentLine.direction) { + const newY = + currentLine.direction === 'top' + ? Math.floor(Math.min(currentLine.y1, currentLine.y2)) + : Math.floor(Math.max(currentLine.y1, currentLine.y2)) + const newPoint1 = { x: currentLineX - length, y: newY } + const newPoint2 = { x: nextLine.x1, y: nextLine.y1 } + rearrangeOuterLine(currentIdx + 1) + drawLine(newPoint1, newPoint2, currentIdx + 1, 'left') + if (Math.abs(currentLineX - prevLine.x1) < 2) { + prevLine.set({ x1: currentLineX - length }) + } else { + prevLine.set({ x2: currentLineX - length }) + } + } else { + if (Math.abs(currentLineX - prevLine.x1) < 2) { + prevLine.set({ x1: prevLine.x1 - length }) + } else { + prevLine.set({ x2: prevLine.x2 - length }) + } + + if (Math.abs(currentLineX - nextLine.x1) < 2) { + nextLine.set({ x1: nextLine.x1 - length }) + } else { + nextLine.set({ x2: nextLine.x2 - length }) + } + } + + currentLine.set({ x1: currentLine.x1 - length, x2: currentLine.x2 - length }) break } case 'right': { + if (prevLine.direction === currentLine.direction) { + const newY = + currentLine.direction === 'top' + ? Math.floor(Math.max(currentLine.y1, currentLine.y2)) + : Math.floor(Math.min(currentLine.y1, currentLine.y2)) + const newPoint1 = { x: currentLineX + length, y: newY } + const newPoint2 = { x: prevLine.x2, y: prevLine.y2 } + rearrangeOuterLine(currentIdx) + drawLine(newPoint1, newPoint2, currentIdx, 'right') + if (Math.abs(currentLineX - nextLine.x1) < 2) { + nextLine.set({ x1: currentLineX + length }) + } else { + nextLine.set({ x2: currentLineX + length }) + } + } else if (nextLine.direction === currentLine.direction) { + const newY = + currentLine.direction === 'top' + ? Math.floor(Math.min(currentLine.y1, currentLine.y2)) + : Math.floor(Math.max(currentLine.y1, currentLine.y2)) + const newPoint1 = { x: currentLineX + length, y: newY } + const newPoint2 = { x: nextLine.x1, y: nextLine.y1 } + rearrangeOuterLine(currentIdx + 1) + drawLine(newPoint1, newPoint2, currentIdx + 1, 'right') + + if (Math.abs(currentLineX - prevLine.x1) < 2) { + prevLine.set({ x1: currentLineX + length }) + } else { + prevLine.set({ x2: currentLineX + length }) + } + } else { + if (Math.abs(currentLineX - prevLine.x1) < 2) { + prevLine.set({ x1: prevLine.x1 + length }) + } else { + prevLine.set({ x2: prevLine.x2 + length }) + } + if (Math.abs(currentLineX - nextLine.x1) < 2) { + nextLine.set({ x1: nextLine.x1 + length }) + } else { + nextLine.set({ x2: nextLine.x2 + length }) + } + } + + currentLine.set({ x1: currentLine.x1 + length, x2: currentLine.x2 + length }) + break } } + canvas.renderAll() } - return { type, setType, buttonMenu, TYPES, length1Ref, length2Ref, radioTypeRef, currentWallLineRef, arrow1Ref, arrow2Ref, handleSave } + return { + type, + setType, + buttonMenu, + TYPES, + length1Ref, + length2Ref, + radioTypeRef, + currentWallLineRef, + arrow1Ref, + arrow2Ref, + handleSave, + wallLineEditRef, + } } diff --git a/src/hooks/surface/usePlacementShapeDrawing.js b/src/hooks/surface/usePlacementShapeDrawing.js new file mode 100644 index 00000000..123e991d --- /dev/null +++ b/src/hooks/surface/usePlacementShapeDrawing.js @@ -0,0 +1,818 @@ +import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' +import { + adsorptionPointAddModeState, + adsorptionPointModeState, + adsorptionRangeState, + canvasState, + dotLineIntervalSelector, + verticalHorizontalModeState, +} from '@/store/canvasAtom' +import { useEvent } from '@/hooks/useEvent' +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 { fabric } from 'fabric' +import { calculateAngle, drawDirectionArrow } from '@/util/qpolygon-utils' +import { + placementShapeDrawingAngle1State, + placementShapeDrawingAngle2State, + placementShapeDrawingArrow1State, + placementShapeDrawingArrow2State, + placementShapeDrawingDiagonalState, + placementShapeDrawingFixState, + placementShapeDrawingLength1State, + placementShapeDrawingLength2State, + placementShapeDrawingPointsState, + placementShapeDrawingTypeState, +} from '@/store/placementShapeDrawingAtom' +import { usePolygon } from '@/hooks/usePolygon' +import { POLYGON_TYPE } from '@/common/common' + +// 면형상 배치 +export function usePlacementShapeDrawing(setShowPlaceShapeDrawingModal) { + const canvas = useRecoilValue(canvasState) + const { addCanvasMouseEventListener, addDocumentEventListener, removeAllMouseEventListeners, removeAllDocumentEventListeners, removeMouseEvent } = + useEvent() + const { getIntersectMousePoint } = useMouse() + const { addLine, removeLine } = useLine() + const { addPolygonByLines } = usePolygon() + const { tempGridMode } = useTempGrid() + + const verticalHorizontalMode = useRecoilValue(verticalHorizontalModeState) + const adsorptionPointAddMode = useRecoilValue(adsorptionPointAddModeState) + const adsorptionPointMode = useRecoilValue(adsorptionPointModeState) + const adsorptionRange = useRecoilValue(adsorptionRangeState) + const interval = useRecoilValue(dotLineIntervalSelector) // 가로 세로 간격 + + const length1Ref = useRef(null) + const length2Ref = useRef(null) + const angle1Ref = useRef(null) + const angle2Ref = useRef(null) + const [length1, setLength1] = useRecoilState(placementShapeDrawingLength1State) + const [length2, setLength2] = useRecoilState(placementShapeDrawingLength2State) + const [arrow1, setArrow1] = useRecoilState(placementShapeDrawingArrow1State) + const [arrow2, setArrow2] = useRecoilState(placementShapeDrawingArrow2State) + const [points, setPoints] = useRecoilState(placementShapeDrawingPointsState) + const [type, setType] = useRecoilState(placementShapeDrawingTypeState) + const [angle1, setAngle1] = useRecoilState(placementShapeDrawingAngle1State) + const [angle2, setAngle2] = useRecoilState(placementShapeDrawingAngle2State) + const [outerLineDiagonalLength, setOuterLineDiagonalLength] = useRecoilState(placementShapeDrawingDiagonalState) + const setOuterLineFix = useSetRecoilState(placementShapeDrawingFixState) + const arrow1Ref = useRef(arrow1) + const arrow2Ref = useRef(arrow2) + + const outerLineDiagonalLengthRef = useRef(null) + + const isFix = useRef(false) + + useEffect(() => { + if (adsorptionPointAddMode || tempGridMode) { + return + } + + addCanvasMouseEventListener('mouse:down', mouseDown) + clear() + }, [verticalHorizontalMode, points, adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, interval, tempGridMode]) + + useEffect(() => { + arrow1Ref.current = arrow1 + }, [arrow1]) + + useEffect(() => { + arrow2Ref.current = arrow2 + }, [arrow2]) + + useEffect(() => { + clear() + addDocumentEventListener('keydown', document, keydown[type]) + }, [type]) + + const clear = () => { + setLength1(0) + setLength2(0) + + setArrow1('') + setArrow2('') + + setAngle1(0) + setAngle2(0) + + setOuterLineDiagonalLength(0) + } + + const mouseDown = (e) => { + let pointer = getIntersectMousePoint(e) + + if (points.length === 0) { + setPoints((prev) => [...prev, pointer]) + } else { + const lastPoint = points[points.length - 1] + let newPoint = { x: pointer.x, y: pointer.y } + const length = distanceBetweenPoints(lastPoint, newPoint) + if (verticalHorizontalMode) { + const vector = { + x: pointer.x - points[points.length - 1].x, + y: pointer.y - points[points.length - 1].y, + } + const slope = Math.abs(vector.y / vector.x) // 기울기 계산 + + let scaledVector + if (slope >= 1) { + // 기울기가 1 이상이면 x축 방향으로 그림 + scaledVector = { + x: 0, + y: vector.y >= 0 ? Number(length) : -Number(length), + } + } else { + // 기울기가 1 미만이면 y축 방향으로 그림 + scaledVector = { + x: vector.x >= 0 ? Number(length) : -Number(length), + y: 0, + } + } + + const verticalLength = scaledVector.y + const horizontalLength = scaledVector.x + + newPoint = { + x: lastPoint.x + horizontalLength, + y: lastPoint.y + verticalLength, + } + } + setPoints((prev) => [...prev, newPoint]) + } + } + + useEffect(() => { + canvas + ?.getObjects() + .filter((obj) => obj.name === 'placementShapeDrawingLine' || obj.name === 'helpGuideLine') + .forEach((obj) => { + removeLine(obj) + }) + + canvas?.remove(canvas?.getObjects().find((obj) => obj.name === 'placementShapeDrawingStartPoint')) + + if (points.length === 0) { + setOuterLineFix(true) + removeAllDocumentEventListeners() + return + } + + addDocumentEventListener('keydown', document, keydown[type]) + + if (points.length === 1) { + const point = new fabric.Circle({ + radius: 5, + fill: 'transparent', + stroke: 'red', + left: points[0].x - 5, + top: points[0].y - 5, + selectable: false, + name: 'placementShapeDrawingStartPoint', + }) + + canvas?.add(point) + } else { + setOuterLineFix(false) + canvas + .getObjects() + .filter((obj) => obj.name === 'placementShapeDrawingPoint') + .forEach((obj) => { + canvas.remove(obj) + }) + points.forEach((point, idx) => { + const circle = new fabric.Circle({ + left: point.x, + top: point.y, + visible: false, + name: 'placementShapeDrawingPoint', + }) + canvas.add(circle) + }) + points.forEach((point, idx) => { + if (idx === 0) { + return + } + drawLine(points[idx - 1], point, idx) + }) + + const lastPoint = points[points.length - 1] + const firstPoint = points[0] + + if (isFix.current) { + removeAllMouseEventListeners() + removeAllDocumentEventListeners() + + const lines = canvas?.getObjects().filter((obj) => obj.name === 'placementShapeDrawingLine') + const roof = addPolygonByLines(lines, { + stroke: 'black', + strokeWidth: 3, + selectable: true, + name: POLYGON_TYPE.ROOF, + originX: 'center', + originY: 'center', + direction: 'south', + }) + + setSurfaceShapePattern(roof) + drawDirectionArrow(roof) + + lines.forEach((line) => { + removeLine(line) + }) + + canvas?.renderAll() + setShowPlaceShapeDrawingModal(false) + } + + if (points.length < 3) { + return + } + + if (Math.abs(lastPoint.x - firstPoint.x) < 1 && Math.abs(lastPoint.y - firstPoint.y) < 1) { + return + } + + if (Math.abs(lastPoint.x - firstPoint.x) < 1 || Math.abs(lastPoint.y - firstPoint.y) < 1) { + let isAllRightAngle = true + + const firstPoint = points[0] + + points.forEach((point, idx) => { + if (idx === 0 || !isAllRightAngle) { + return + } + + const angle = calculateAngle(point, firstPoint) + if (angle % 90 !== 0) { + isAllRightAngle = false + } + }) + + if (isAllRightAngle) { + return + } + const line = addLine([lastPoint.x, lastPoint.y, firstPoint.x, firstPoint.y], { + stroke: 'grey', + strokeWidth: 1, + selectable: false, + name: 'helpGuideLine', + }) + } else { + const guideLine1 = addLine([lastPoint.x, lastPoint.y, lastPoint.x, firstPoint.y], { + stroke: 'grey', + strokeWidth: 1, + strokeDashArray: [1, 1, 1], + name: 'helpGuideLine', + }) + + const guideLine2 = addLine([guideLine1.x2, guideLine1.y2, firstPoint.x, firstPoint.y], { + stroke: 'grey', + strokeWidth: 1, + strokeDashArray: [1, 1, 1], + name: 'helpGuideLine', + }) + } + } + }, [points]) + + const drawLine = (point1, point2, idx) => { + addLine([point1.x, point1.y, point2.x, point2.y], { + stroke: 'black', + strokeWidth: 3, + idx: idx, + selectable: true, + name: 'placementShapeDrawingLine', + x1: point1.x, + y1: point1.y, + x2: point2.x, + y2: point2.y, + }) + } + + // 직각 완료될 경우 확인 + const checkRightAngle = (direction) => { + const activeElem = document.activeElement + + const canDirection = + direction === '↓' || direction === '↑' + ? arrow1Ref.current === '←' || arrow1Ref.current === '→' + : arrow1Ref.current === '↓' || arrow1Ref.current === '↑' + + if (activeElem === length1Ref.current || activeElem === angle1Ref.current) { + setArrow1(direction) + arrow1Ref.current = direction + length2Ref.current.focus() + } else if (activeElem === length2Ref.current || activeElem === angle2Ref.current) { + if (!canDirection) { + return + } + setArrow2(direction) + arrow2Ref.current = direction + } + + const length1Num = Number(length1Ref.current.value) / 10 + const length2Num = Number(length2Ref.current.value) / 10 + + if (points.length === 0) { + return + } + + if (length1Num === 0 || length2Num === 0 || arrow1Ref.current === '' || arrow2Ref.current === '') { + return + } + + if (arrow1Ref.current === '↓' && arrow2Ref.current === '→') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x + length2Num, y: prev[prev.length - 1].y + length1Num }] + }) + } else if (arrow1Ref.current === '↓' && arrow2Ref.current === '←') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x - length2Num, y: prev[prev.length - 1].y + length1Num }] + }) + } else if (arrow1Ref.current === '↑' && arrow2Ref.current === '→') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x + length2Num, y: prev[prev.length - 1].y - length1Num }] + }) + } else if (arrow1Ref.current === '↑' && arrow2Ref.current === '←') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x - length2Num, y: prev[prev.length - 1].y - length1Num }] + }) + } else if (arrow1Ref.current === '→' && arrow2Ref.current === '↓') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x + length1Num, y: prev[prev.length - 1].y + length2Num }] + }) + } else if (arrow1Ref.current === '→' && arrow2Ref.current === '↑') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x + length1Num, y: prev[prev.length - 1].y - length2Num }] + }) + } else if (arrow1Ref.current === '←' && arrow2Ref.current === '↓') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x - length1Num, y: prev[prev.length - 1].y + length2Num }] + }) + } else if (arrow1Ref.current === '←' && arrow2Ref.current === '↑') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x - length1Num, y: prev[prev.length - 1].y - length2Num }] + }) + } + } + + //이구배 완료될 경우 확인 ↓, ↑, ←, → + const checkDoublePitch = (direction) => { + const activeElem = document.activeElement + + const canDirection = + direction === '↓' || direction === '↑' + ? arrow1Ref.current === '←' || arrow1Ref.current === '→' + : arrow1Ref.current === '↓' || arrow1Ref.current === '↑' + + if (activeElem === length1Ref.current || activeElem === angle1Ref.current) { + setArrow1(direction) + arrow1Ref.current = direction + angle2Ref.current.focus() + } else if (activeElem === length2Ref.current || activeElem === angle2Ref.current) { + if (!canDirection) { + return + } + setArrow2(direction) + arrow2Ref.current = direction + } + + const angle1Value = angle1Ref.current.value + const angle2Value = angle2Ref.current.value + const length1Value = length1Ref.current.value + const length2Value = length2Ref.current.value + + const arrow1Value = arrow1Ref.current + const arrow2Value = arrow2Ref.current + + if (angle1Value !== 0 && length1Value !== 0 && angle2Value !== 0 && arrow1Value !== '' && arrow2Value !== '') { + if (arrow1Value === '↓' && arrow2Value === '→') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10 }] + }) + } else if (arrow1Value === '↓' && arrow2Value === '←') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x - length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10 }] + }) + } else if (arrow1Value === '↑' && arrow2Value === '→') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y - length2Value / 10 }] + }) + } else if (arrow1Value === '↑' && arrow2Value === '←') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x - length1Value / 10, y: prev[prev.length - 1].y - length2Value / 10 }] + }) + } else if (arrow1Value === '→' && arrow2Value === '↓') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }] + }) + } else if (arrow1Value === '→' && arrow2Value === '↑') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }] + }) + } else if (arrow1Value === '←' && arrow2Value === '↓') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }] + }) + } else if (arrow1Value === '←' && arrow2Value === '↑') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }] + }) + } + + angle1Ref.current.focus() + } + } + + //대각선 완료될 경우 확인 + const checkDiagonal = (direction) => { + const activeElem = document.activeElement + const diagonalLength = outerLineDiagonalLengthRef.current.value // 대각선 길이 + + const length1Value = length1Ref.current.value + + if (diagonalLength <= length1Value) { + alert('대각선 길이는 직선 길이보다 길어야 합니다.') + return + } + + const canDirection = + direction === '↓' || direction === '↑' + ? arrow1Ref.current === '←' || arrow1Ref.current === '→' + : arrow1Ref.current === '↓' || arrow1Ref.current === '↑' + + if (activeElem === length1Ref.current) { + setArrow1(direction) + arrow1Ref.current = direction + } else if (activeElem === length2Ref.current || activeElem === angle2Ref.current) { + if (!canDirection) { + return + } + setArrow2(direction) + arrow2Ref.current = direction + } + + const arrow1Value = arrow1Ref.current + const arrow2Value = arrow2Ref.current + + const getLength2 = () => { + return Math.floor(Math.sqrt(diagonalLength ** 2 - length1Value ** 2)) + } + + const length2Value = getLength2() + + if (diagonalLength !== 0 && length1Value !== 0 && arrow1Value !== '') { + setLength2(getLength2()) + length2Ref.current.focus() + } + + if (length1Value !== 0 && length2Value !== 0 && arrow1Value !== '' && arrow2Value !== '') { + if (arrow1Value === '↓' && arrow2Value === '→') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }] + }) + } else if (arrow1Value === '↓' && arrow2Value === '←') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }] + }) + } else if (arrow1Value === '↑' && arrow2Value === '→') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }] + }) + } else if (arrow1Value === '↑' && arrow2Value === '←') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }] + }) + } else if (arrow1Value === '→' && arrow2Value === '↓') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10 }] + }) + } else if (arrow1Value === '→' && arrow2Value === '↑') { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [ + ...prev, + { + x: prev[prev.length - 1].x + length1Value / 10, + y: prev[prev.length - 1].y - length2Value / 10, + }, + ] + }) + } + } + } + + const keydown = { + outerLine: (e) => { + if (points.length === 0) { + return + } + // 포커스가 length1에 있지 않으면 length1에 포커스를 줌 + const activeElem = document.activeElement + if (activeElem !== length1Ref.current) { + length1Ref.current.focus() + } + + const key = e.key + + if (!length1Ref.current) { + return + } + + const lengthNum = Number(length1Ref.current.value) / 10 + if (lengthNum === 0) { + return + } + switch (key) { + case 'Down': // IE/Edge에서 사용되는 값 + case 'ArrowDown': { + setArrow1('↓') + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x, y: prev[prev.length - 1].y + lengthNum }] + }) + break + } + case 'Up': // IE/Edge에서 사용되는 값 + case 'ArrowUp': + setArrow1('↑') + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x, y: prev[prev.length - 1].y - lengthNum }] + }) + break + case 'Left': // IE/Edge에서 사용되는 값 + case 'ArrowLeft': + setArrow1('←') + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x - lengthNum, y: prev[prev.length - 1].y }] + }) + break + case 'Right': // IE/Edge에서 사용되는 값 + case 'ArrowRight': + setArrow1('→') + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + return [...prev, { x: prev[prev.length - 1].x + lengthNum, y: prev[prev.length - 1].y }] + }) + break + } + }, + rightAngle: (e) => { + if (points.length === 0) { + return + } + const key = e.key + + const activeElem = document.activeElement + if (activeElem !== length1Ref.current && activeElem !== length2Ref.current) { + length1Ref.current.focus() + } + + switch (key) { + case 'Down': // IE/Edge에서 사용되는 값 + case 'ArrowDown': { + checkRightAngle('↓') + break + } + case 'Up': // IE/Edge에서 사용되는 값 + case 'ArrowUp': + checkRightAngle('↑') + break + case 'Left': // IE/Edge에서 사용되는 값 + case 'ArrowLeft': + checkRightAngle('←') + break + case 'Right': // IE/Edge에서 사용되는 값 + case 'ArrowRight': + checkRightAngle('→') + break + case 'Enter': + break + } + }, + doublePitch: (e) => { + if (points.length === 0) { + return + } + const key = e.key + switch (key) { + case 'Down': // IE/Edge에서 사용되는 값 + case 'ArrowDown': { + checkDoublePitch('↓') + break + } + case 'Up': // IE/Edge에서 사용되는 값 + case 'ArrowUp': + checkDoublePitch('↑') + break + case 'Left': // IE/Edge에서 사용되는 값 + case 'ArrowLeft': + checkDoublePitch('←') + break + case 'Right': // IE/Edge에서 사용되는 값 + case 'ArrowRight': + checkDoublePitch('→') + break + } + }, + angle: (e) => { + if (points.length === 0) { + return + } + const key = e.key + switch (key) { + case 'Enter': { + setPoints((prev) => { + if (prev.length === 0) { + return [] + } + const lastPoint = prev[prev.length - 1] + const length = length1Ref.current.value / 10 + const angle = angle1Ref.current.value + //lastPoint로부터 angle1만큼의 각도로 length1만큼의 길이를 가지는 선을 그림 + const radian = (angle * Math.PI) / 180 + + const x = lastPoint.x + length * Math.cos(radian) + const y = lastPoint.y - length * Math.sin(radian) + return [...prev, { x, y }] + }) + } + } + }, + diagonalLine: (e) => { + if (points.length === 0) { + return + } + + const key = e.key + switch (key) { + case 'Down': // IE/Edge에서 사용되는 값 + case 'ArrowDown': { + checkDiagonal('↓') + break + } + case 'Up': // IE/Edge에서 사용되는 값 + case 'ArrowUp': + checkDiagonal('↑') + break + case 'Left': // IE/Edge에서 사용되는 값 + case 'ArrowLeft': + checkDiagonal('←') + break + case 'Right': // IE/Edge에서 사용되는 값 + case 'ArrowRight': + checkDiagonal('→') + break + } + }, + } + + /** + * 일변전으로 돌아가기 + */ + const handleRollback = () => { + //points의 마지막 요소를 제거 + setPoints((prev) => prev.slice(0, prev.length - 1)) + } + + const handleFix = () => { + if (points.length < 3) { + return + } + + let isAllRightAngle = true + + const firstPoint = points[0] + + points.forEach((point, idx) => { + if (idx === 0 || !isAllRightAngle) { + return + } + + const angle = calculateAngle(point, firstPoint) + if (angle % 90 !== 0) { + isAllRightAngle = false + } + }) + + if (isAllRightAngle) { + alert('부정확한 다각형입니다.') + return + } + + setPoints((prev) => { + return [...prev, { x: prev[0].x, y: prev[0].y }] + }) + + isFix.current = true + } + + return { + points, + setPoints, + length1, + setLength1, + length2, + setLength2, + length1Ref, + length2Ref, + arrow1, + setArrow1, + arrow2, + setArrow2, + arrow1Ref, + arrow2Ref, + angle1, + setAngle1, + angle1Ref, + angle2, + setAngle2, + angle2Ref, + outerLineDiagonalLength, + setOuterLineDiagonalLength, + outerLineDiagonalLengthRef, + type, + setType, + handleFix, + handleRollback, + } +} diff --git a/src/hooks/surface/useSurfaceShapeBatch.js b/src/hooks/surface/useSurfaceShapeBatch.js index b52781b6..7eb421cc 100644 --- a/src/hooks/surface/useSurfaceShapeBatch.js +++ b/src/hooks/surface/useSurfaceShapeBatch.js @@ -1,21 +1,21 @@ 'use client' -import { useEffect, useRef, useState } from 'react' -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' +import { useRecoilValue } from 'recoil' import { canvasState } from '@/store/canvasAtom' -import { MENU } from '@/common/common' -import { getIntersectionPoint } from '@/util/canvas-util' +import { MENU, BATCH_TYPE, POLYGON_TYPE } from '@/common/common' +import { getIntersectionPoint, setSurfaceShapePattern } from '@/util/canvas-util' import { degreesToRadians } from '@turf/turf' import { QPolygon } from '@/components/fabric/QPolygon' -import { fabric } from 'fabric' import { useSwal } from '@/hooks/useSwal' import { useMessage } from '@/hooks/useMessage' +import { useEvent } from '@/hooks/useEvent' export function useSurfaceShapeBatch() { const { getMessage } = useMessage() const canvas = useRecoilValue(canvasState) const { swalFire } = useSwal() + const { addCanvasMouseEventListener, initEvent } = useEvent() const applySurfaceShape = (surfaceRefs, selectedType, setShowPlacementSurfaceSettingModal) => { let length1, length2, length3, length4, length5 @@ -57,7 +57,7 @@ export function useSurfaceShapeBatch() { let points = [] if (checkSurfaceShape(surfaceId, { length1, length2, length3, length4, length5 })) { setShowPlacementSurfaceSettingModal(false) - canvas?.on('mouse:move', (e) => { + addCanvasMouseEventListener('mouse:move', (e) => { if (!isDrawing) { return } @@ -110,15 +110,13 @@ export function useSurfaceShapeBatch() { obj.set({ direction: direction }) obj.set({ originAngle: originAngle }) - // setCurrentPattern(obj) canvas?.renderAll() }) - canvas?.on('mouse:down', (e) => { + addCanvasMouseEventListener('mouse:down', (e) => { isDrawing = false - obj.set('name', MENU.BATCH_CANVAS.SURFACE_SHAPE_BATCH) - canvas?.off('mouse:down') - canvas.off('mouse:move') + obj.set('name', POLYGON_TYPE.ROOF) + initEvent() setSurfaceShapePattern(obj) setShowPlacementSurfaceSettingModal(true) }) @@ -132,23 +130,23 @@ export function useSurfaceShapeBatch() { if (surfaceId === 1) { if (length1 === 0) { - swalFire({ text: getMessage('surface.shape.validate.size'), icon: 'error' }) + swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' }) check = false } if (length2 === 0) { if (length3 === 0) { - swalFire({ text: getMessage('surface.shape.validate.size'), icon: 'error' }) + swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' }) check = false } } } else if ([2, 4].includes(surfaceId)) { if (length1 === 0 || length2 === 0) { - swalFire({ text: getMessage('surface.shape.validate.size'), icon: 'error' }) + swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' }) check = false } } else if ([3, 5, 6, 15, 18].includes(surfaceId)) { if (length1 === 0 || length2 === 0 || length3 === 0) { - swalFire({ text: getMessage('surface.shape.validate.size'), icon: 'error' }) + swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' }) check = false } if (surfaceId === 3 && length3 > length1) { @@ -174,7 +172,7 @@ export function useSurfaceShapeBatch() { } } else if ([8, 12, 13, 16, 17].includes(surfaceId)) { if (length1 === 0 || length2 === 0 || length3 === 0 || length4 === 0) { - swalFire({ text: getMessage('surface.shape.validate.size'), icon: 'error' }) + swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' }) check = false } @@ -202,7 +200,7 @@ export function useSurfaceShapeBatch() { } } else if ([7, 9, 10, 11, 14].includes(surfaceId)) { if (length1 === 0 || length2 === 0 || length3 === 0 || length4 === 0 || length5 === 0) { - swalFire({ text: getMessage('surface.shape.validate.size'), icon: 'error' }) + swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' }) check = false } if (surfaceId === 9 || surfaceId === 10 || surfaceId === 11) { @@ -566,66 +564,32 @@ export function useSurfaceShapeBatch() { return points } - //면형상 선택 클릭시 지붕 패턴 입히기 - const setSurfaceShapePattern = (polygon) => { - const ratio = window.devicePixelRatio || 1 - - let width = 265 / 10 - let height = 150 / 10 - 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') - const 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 = 'green' - ctx.lineWidth = 0.4 - - 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() - - for (let col = 0; col <= cols; col++) { - const x = col * patternSize.width + (row % 2 === 0 ? 0 : offset) - const yStart = row * patternSize.height - const yEnd = yStart + patternSize.height - - ctx.beginPath() - ctx.moveTo(x, yStart) // 선 시작점 - ctx.lineTo(x, yEnd) // 선 끝점 - ctx.stroke() - } - } - - // 패턴 생성 - const pattern = new fabric.Pattern({ - source: patternSourceCanvas, - repeat: 'repeat', + const deleteAllSurfacesAndObjects = () => { + swalFire({ + text: '배치면 내용을 전부 삭제하시겠습니까?', + type: 'confirm', + confirmFn: () => { + canvas?.getObjects().forEach((obj) => { + if ( + obj.name === POLYGON_TYPE.ROOF || + obj.name === BATCH_TYPE.OPENING || + obj.name === BATCH_TYPE.SHADOW || + obj.name === BATCH_TYPE.TRIANGLE_DORMER || + obj.name === BATCH_TYPE.PENTAGON_DORMER + ) { + canvas?.remove(obj) + } + }) + swalFire({ text: '삭제 완료 되었습니다.' }) + }, + // denyFn: () => { + // swalFire({ text: '취소되었습니다.', icon: 'error' }) + // }, }) - - polygon.set('fill', null) - polygon.set('fill', pattern) - canvas?.renderAll() } return { applySurfaceShape, + deleteAllSurfacesAndObjects, } } diff --git a/src/hooks/useAxios.js b/src/hooks/useAxios.js index 39b71769..f2e8add9 100644 --- a/src/hooks/useAxios.js +++ b/src/hooks/useAxios.js @@ -40,56 +40,60 @@ export function useAxios(lang = '') { // response 추가 로직 axios.interceptors.request.use(undefined, (error) => {}) - const get = async ({ url }) => { + const get = async ({ url, option = {} }) => { return await getInstances(url) - .get(url) + .get(url, option) .then((res) => res.data) .catch(console.error) } - const promiseGet = async ({ url }) => { - return await getInstances(url).get(url) + const promiseGet = async ({ url, option = {} }) => { + return await getInstances(url).get(url, option) } - const post = async ({ url, data }) => { + const post = async ({ url, data, option = {} }) => { return await getInstances(url) - .post(url, data) + .post(url, data, option) .then((res) => res.data) .catch(console.error) } - const promisePost = async ({ url, data }) => { - return await getInstances(url).post(url, data) + const promisePost = async ({ url, data, option = {} }) => { + return await getInstances(url).post(url, data, option) } - const put = async ({ url, data }) => { + const put = async ({ url, data, option = {} }) => { return await getInstances(url) - .put(url, data) + .put(url, data, option) .then((res) => res.data) .catch(console.error) } - const promisePut = async ({ url, data }) => { - return await getInstances(url).put(url, data) + const promisePut = async ({ url, data, option = {} }) => { + return await getInstances(url).put(url, data, option) } - const patch = async ({ url, data }) => { + const patch = async ({ url, data, option = {} }) => { return await getInstances(url) - .patch(url, data) + .patch(url, data, option) .then((res) => res.data) .catch(console.error) } - const del = async ({ url }) => { + const promisePatch = async ({ url, data, option = {} }) => { + return await getInstances(url).patch(url, data, option) + } + + const del = async ({ url, option = {} }) => { return await getInstances(url) - .delete(url) + .delete(url, option) .then((res) => res.data) .catch(console.error) } - const promiseDel = async ({ url }) => { - return await getInstances(url).delete(url) + const promiseDel = async ({ url, option = {} }) => { + return await getInstances(url).delete(url, option) } - return { get, promiseGet, post, promisePost, put, promisePut, patch, del, promiseDel } + return { get, promiseGet, post, promisePost, put, promisePut, patch, promisePatch, del, promiseDel } } diff --git a/src/hooks/useCanvasEvent.js b/src/hooks/useCanvasEvent.js index 55a34cb2..cf3b6244 100644 --- a/src/hooks/useCanvasEvent.js +++ b/src/hooks/useCanvasEvent.js @@ -1,6 +1,6 @@ import { useState } from 'react' import { useRecoilState, useRecoilValue } from 'recoil' -import { canvasSizeState, currentObjectState } from '@/store/canvasAtom' +import { canvasSizeState, currentObjectState, fontFamilyState, fontSizeState } from '@/store/canvasAtom' import { QPolygon } from '@/components/fabric/QPolygon' // 캔버스에 필요한 이벤트 @@ -8,6 +8,8 @@ export function useCanvasEvent() { const [canvas, setCanvasForEvent] = useState(null) const [currentObject, setCurrentObject] = useRecoilState(currentObjectState) const canvasSize = useRecoilValue(canvasSizeState) + const fontSize = useRecoilValue(fontSizeState) + const fontFamily = useRecoilValue(fontFamilyState) // 기본적인 이벤트 필요시 추가 const attachDefaultEventOnCanvas = () => { @@ -74,9 +76,16 @@ export function useCanvasEvent() { }) } + if (target.type.toLowerCase().includes('text')) { + target.set({ fontSize }) + target.set({ fontFamily }) + } + if (target.name === 'lengthText') { const x = target.left const y = target.top + target.lockMovementX = false + target.lockMovementY = false // Add a property to store the previous value const previousValue = target.text target.on('selected', (e) => { diff --git a/src/hooks/useContextMenu.js b/src/hooks/useContextMenu.js new file mode 100644 index 00000000..665617ef --- /dev/null +++ b/src/hooks/useContextMenu.js @@ -0,0 +1,160 @@ +import { useRecoilValue } from 'recoil' +import { currentMenuState } from '@/store/canvasAtom' +import { useEffect, useState } from 'react' +import { MENU } from '@/common/common' +import AuxiliaryMove from '@/components/floor-plan/modal/auxiliary/AuxiliaryMove' +import AuxiliarySize from '@/components/floor-plan/modal/auxiliary/AuxiliarySize' + +export function useContextMenu() { + const currentMenu = useRecoilValue(currentMenuState) + const [contextMenu, setContextMenu] = useState([[]]) + const [currentContextMenu, setCurrentContextMenu] = useState(null) + + useEffect(() => { + switch (currentMenu) { + case MENU.PLAN_DRAWING: + setContextMenu([ + [ + { + id: 'gridMove', + name: '그리드 이동', + }, + { + id: 'gridCopy', + name: '그리드 복사', + }, + { + id: 'gridColorEdit', + name: '그리드 색 변경', + }, + { + id: 'remove', + name: '삭제', + }, + { + id: 'removeAll', + name: '전체 삭제', + }, + ], + ]) + break + case MENU.ROOF_COVERING.EXTERIOR_WALL_LINE: + case MENU.ROOF_COVERING.ROOF_SHAPE_SETTINGS: + case MENU.ROOF_COVERING.ROOF_SHAPE_PASSIVITY_SETTINGS: + case MENU.ROOF_COVERING.ROOF_SHAPE_EDITING: + case MENU.ROOF_COVERING.HELP_LINE_DRAWING: + case MENU.ROOF_COVERING.EAVES_KERAVA_EDIT: + case MENU.ROOF_COVERING.MOVEMENT_SHAPE_UPDOWN: + case MENU.ROOF_COVERING.OUTLINE_EDIT_OFFSET: + case MENU.ROOF_COVERING.ROOF_SHAPE_ALLOC: + case MENU.ROOF_COVERING.DEFAULT: + setContextMenu([ + [ + { + id: 'roofMaterialPlacement', + name: '지붕재 배치', + }, + { + id: 'roofMaterialRemove', + name: '지붕재 삭제', + }, + { + id: 'roofMaterialRemoveAll', + name: '지붕재 전체 삭제', + }, + { + id: 'selectMove', + name: '선택・이동', + }, + { + id: 'wallLineRemove', + name: '외벽선 삭제', + }, + ], + [ + { + id: 'sizeEdit', + name: '사이즈 변경', + component: , + }, + { + id: 'auxiliaryMove', + name: '보조선 이동(M)', + component: , + }, + { + id: 'auxiliaryCopy', + name: '보조선 복사(C)', + }, + { + id: 'auxiliaryRemove', + name: '보조선 삭제(D)', + }, + { + id: 'auxiliaryVerticalBisector', + name: '보조선 수직이등분선', + }, + { + id: 'auxiliaryCut', + name: '보조선 절삭', + }, + { + id: 'auxiliaryRemoveAll', + name: '보조선 전체 삭제', + }, + ], + ]) + break + case MENU.BATCH_CANVAS.SLOPE_SETTING: + case MENU.BATCH_CANVAS.BATCH_DRAWING: + case MENU.BATCH_CANVAS.SURFACE_SHAPE_BATCH: + case MENU.BATCH_CANVAS.OBJECT_BATCH: + case MENU.BATCH_CANVAS.ALL_REMOVE: + case MENU.BATCH_CANVAS.DEFAULT: + setContextMenu([ + [ + { + id: 'sizeEdit', + name: '사이즈 변경', + }, + { + id: 'remove', + name: '삭제(D)', + }, + { + id: 'move', + name: '이동(M)', + }, + { + id: 'copy', + name: '복사(C)', + }, + ], + [ + { + id: 'roofMaterialEdit', + name: '지붕재 변경', + }, + { + id: 'linePropertyEdit', + name: '각 변 속성 변경', + }, + { + id: 'flowDirectionEdit', + name: '흐름 방향 변경', + }, + ], + ]) + break + default: + setContextMenu([]) + break + } + }, [currentMenu]) + + return { + contextMenu, + currentContextMenu, + setCurrentContextMenu, + } +} diff --git a/src/hooks/useEvent.js b/src/hooks/useEvent.js index 639b61b3..d14fa7e5 100644 --- a/src/hooks/useEvent.js +++ b/src/hooks/useEvent.js @@ -43,7 +43,6 @@ export function useEvent() { //default Event 추가 addCanvasMouseEventListener('mouse:move', defaultMouseMoveEvent) addCanvasMouseEventListener('mouse:out', defaultMouseOutEvent) - addDocumentEventListener('keydown', document, defaultKeyboardEvent) addDocumentEventListener('contextmenu', document, defaultContextMenuEvent) if (adsorptionPointAddMode) { addCanvasMouseEventListener('mouse:down', adsorptionPointAddModeStateEvent) diff --git a/src/hooks/useLine.js b/src/hooks/useLine.js index f2941e33..a313d127 100644 --- a/src/hooks/useLine.js +++ b/src/hooks/useLine.js @@ -14,7 +14,7 @@ export const useLine = () => { fontFamily: fontFamily, }) - if (line.length === 0) { + if (line.length < 1) { return null } diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index 6a332d35..13b6693a 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -1713,6 +1713,7 @@ export function useMode() { const drawRoofPolygon = (wall) => { const polygon = createRoofPolygon(wall.points) const originPolygon = new QPolygon(wall.points, { fontSize: 0 }) + originPolygon.setViewLengthText(false) let offsetPolygon let result = createMarginPolygon(polygon, wall.lines).vertices diff --git a/src/hooks/usePagination.js b/src/hooks/usePagination.js new file mode 100644 index 00000000..7d76d293 --- /dev/null +++ b/src/hooks/usePagination.js @@ -0,0 +1,34 @@ +import { useEffect, useState } from 'react' + +/** + * 페이지네이션 훅 + * @param {number} pageNo 현재 페이지 {optional} + * @param {number} pageSize 페이지당 아이템 수 {optional} + * @param {number} pagePerBlock 페이지 그룹 수 {optional} + * @param {number} totalCount 전체 아이템 수 {required} + * @returns + */ +const usePagination = ({ pageNo = 1, pageSize = 10, pagePerBlock = 10, totalCount = 0 }) => { + const [currentPage, setCurrentPage] = useState(pageNo) + const changePage = (page) => { + setCurrentPage(page) + } + + useEffect(() => { + setCurrentPage(pageNo) + }, [pageNo]) + + const pageGroup = Math.floor((currentPage - 1) / pagePerBlock) + 1 + const totalPages = Math.ceil(totalCount / pageSize) + const pages = Array.from({ length: totalPages }, (_, i) => i + 1) + const startPage = Math.max(1, (pageGroup - 1) * pagePerBlock + 1) + const endPage = Math.min(totalPages, pageGroup * pagePerBlock) + const pageRange = Array.from({ length: endPage !== totalPages ? pagePerBlock : endPage - startPage + 1 }, (_, i) => { + if (i + startPage > endPage) return + return i + startPage + }) + + return { currentPage, changePage, pageGroup, totalPages, pages, startPage, endPage, pageRange } +} + +export default usePagination diff --git a/src/hooks/usePlan.js b/src/hooks/usePlan.js index c7f6620e..8afb2060 100644 --- a/src/hooks/usePlan.js +++ b/src/hooks/usePlan.js @@ -1,3 +1,4 @@ +import { useEffect, useState } from 'react' import { useRecoilState } from 'recoil' import { canvasState, currentCanvasPlanState, initCanvasPlansState, plansState } from '@/store/canvasAtom' import { useAxios } from '@/hooks/useAxios' @@ -5,10 +6,13 @@ import { useMessage } from '@/hooks/useMessage' import { useSwal } from '@/hooks/useSwal' export function usePlan() { + const [planNum, setPlanNum] = useState(0) + const [canvas, setCanvas] = useRecoilState(canvasState) const [currentCanvasPlan, setCurrentCanvasPlan] = useRecoilState(currentCanvasPlanState) - const [initCanvasPlans, setInitCanvasPlans] = useRecoilState(initCanvasPlansState) - const [plans, setPlans] = useRecoilState(plansState) + const [initCanvasPlans, setInitCanvasPlans] = useRecoilState(initCanvasPlansState) // DB에 저장된 plan + const [plans, setPlans] = useRecoilState(plansState) // 전체 plan (DB에 저장된 plan + 저장 안된 새로운 plan) + const { swalFire } = useSwal() const { getMessage } = useMessage() const { get, promisePost, promisePut, promiseDel } = useAxios() @@ -54,6 +58,7 @@ export function usePlan() { 'y2', 'attributes', 'stickeyPoint', + 'text', ]) const str = JSON.stringify(objs) @@ -71,122 +76,68 @@ export function usePlan() { // }, 1000) } + /** + * 현재 캔버스에 그려진 데이터를 추출 + */ + const currentCanvasData = () => { + removeMouseLines() + return addCanvas() + } + /** * 실시간 캔버스 상태와 DB에 저장된 캔버스 상태를 비교하여 수정 여부를 판단 */ const checkModifiedCanvasPlan = () => { - removeMouseLines() - const canvasStr = addCanvas() - const canvasStatus = dbToCanvasFormat(canvasToDbFormat(canvasStr)) + const canvasStatus = currentCanvasData() const initPlanData = initCanvasPlans.find((plan) => plan.id === currentCanvasPlan.id) - if (JSON.parse(canvasStr).objects.length === 0 && initPlanData === undefined) { - // 저장 안 된 빈 캔버스 - return false - } else if (initPlanData && canvasStatus === initPlanData.canvasStatus) { - // 저장 되어있고 변경사항 없는 캔버스 - return false + + if (!initPlanData) { + // 새로운 캔버스 + return JSON.parse(canvasStatus).objects.length > 0 } else { - return true + // 저장된 캔버스 + // 각각 object들의 id 목록을 추출하여 비교 + const canvasObjsIds = getObjectIds(JSON.parse(canvasStatus).objects) + const dbObjsIds = getObjectIds(JSON.parse(initPlanData.canvasStatus).objects) + return canvasObjsIds.length !== dbObjsIds.length || !canvasObjsIds.every((id, index) => id === dbObjsIds[index]) } } + const getObjectIds = (objects) => { + return objects + .filter((obj) => obj.hasOwnProperty('id')) + .map((obj) => obj.id) + .sort() + } /** * DB에 저장된 데이터를 canvas에서 사용할 수 있도록 포맷화 */ const dbToCanvasFormat = (cs) => { - return JSON.stringify(cs.replace(/##/g, '"').replace(/\\/g, '').slice(1, -1)) + return cs.replace(/##/g, '"') } /** * canvas의 데이터를 DB에 저장할 수 있도록 포맷화 */ const canvasToDbFormat = (cs) => { - return JSON.stringify(cs).replace(/"/g, '##') + return cs.replace(/"/g, '##') } /** - * 페이지 내 캔버스를 저장하는 함수 - * - * 1. 신규 저장 : POST - * param(body) : userId, objectNo, canvasStatus - * 2. 수정 저장 : PUT - * param(body) : id, canvasStatus + * 페이지 내 캔버스를 저장 */ const saveCanvas = async (userId) => { - removeMouseLines() - const canvasStatus = addCanvas() - - if (initCanvasPlans.some((plan) => plan.id === currentCanvasPlan.id)) { - // canvas 수정 - const planData = { - id: currentCanvasPlan.id, - canvasStatus: canvasToDbFormat(canvasStatus), - } - - return await promisePut({ url: '/api/canvas-management/canvas-statuses', data: planData }) - .then((res) => { - swalFire({ text: getMessage('common.message.save') }) - // console.log('[PUT] canvas-statuses res :::::::: %o', res) - setInitCanvasPlans((initCanvasPlans) => - initCanvasPlans.map((plan) => (plan.id === currentCanvasPlan.id ? { ...plan, canvasStatus: canvasStatus } : plan)), - ) - setPlans((plans) => - plans.map((plan) => (plan.id === currentCanvasPlan.id ? { ...plan, canvasStatus: canvasStatus } : plan)), - ) - }) - .catch((error) => { - swalFire({ text: error.message, icon: 'error' }) - // console.error('[PUT] canvas-statuses error :::::::: %o', error) - }) - } else { - // canvas 신규 등록 - const planData = { - userId: userId, - imageName: 'image_name', // api 필수항목이여서 임시로 넣음, 이후 삭제 필요 - objectNo: currentCanvasPlan.objectNo, - canvasStatus: canvasToDbFormat(canvasStatus), - } - - return await promisePost({ url: '/api/canvas-management/canvas-statuses', data: planData }) - .then((res) => { - swalFire({ text: getMessage('common.message.save') }) - // console.log('[POST] canvas-statuses response :::::::: %o', res) - setInitCanvasPlans((initCanvasPlans) => [ - ...initCanvasPlans, - { - id: res.data, - name: currentCanvasPlan.objectNo + '-' + res.data, - userId: userId, - canvasStatus: canvasStatus, - isNew: currentCanvasPlan.id, - }, - ]) - setPlans((plans) => - plans.map((plan) => - plan.id === currentCanvasPlan.id - ? { - ...plan, - id: res.data, - name: currentCanvasPlan.objectNo + '-' + res.data, - userId: userId, - canvasStatus: canvasStatus, - isNew: currentCanvasPlan.id, - } - : plan, - ), - ) - }) - .catch((error) => { - swalFire({ text: error.message, icon: 'error' }) - // console.error('[POST] canvas-statuses res error :::::::: %o', error) - }) - } + const canvasStatus = currentCanvasData() + initCanvasPlans.some((plan) => plan.id === currentCanvasPlan.id) + ? await putCanvasStatus(canvasStatus) + : await postCanvasStatus(userId, canvasStatus) } /** - * objectNo에 해당하는 canvas 목록을 조회하는 함수 + * objectNo에 해당하는 canvas 목록을 조회 */ const getCanvasByObjectNo = async (userId, objectNo) => { + // console.log(`[GET] objectNo: ${objectNo} / userId: ${userId}`) return get({ url: `/api/canvas-management/canvas-statuses/by-object/${objectNo}/${userId}` }).then((res) => res.map((item) => ({ id: item.id, @@ -199,26 +150,192 @@ export function usePlan() { } /** - * id에 해당하는 canvas 데이터를 삭제하는 함수 + * canvas 데이터를 추가 + */ + const postCanvasStatus = async (userId, canvasStatus) => { + const planData = { + userId: userId, + imageName: 'image_name', // api 필수항목이여서 임시로 넣음, 이후 삭제 필요 + objectNo: currentCanvasPlan.objectNo, + canvasStatus: canvasToDbFormat(canvasStatus), + } + await promisePost({ url: '/api/canvas-management/canvas-statuses', data: planData }) + .then((res) => { + swalFire({ text: getMessage('plan.message.save') }) + setInitCanvasPlans((initCanvasPlans) => [...initCanvasPlans, { id: res.data, canvasStatus: canvasStatus }]) + setPlans((plans) => + plans.map((plan) => + plan.id === currentCanvasPlan.id + ? { + ...plan, + id: res.data, + name: currentCanvasPlan.objectNo + '-' + res.data, + canvasStatus: canvasStatus, + } + : plan, + ), + ) + }) + .catch((error) => { + swalFire({ text: error.message, icon: 'error' }) + }) + } + + /** + * id에 해당하는 canvas 데이터를 수정 + */ + const putCanvasStatus = async (canvasStatus) => { + const planData = { + id: currentCanvasPlan.id, + canvasStatus: canvasToDbFormat(canvasStatus), + } + await promisePut({ url: '/api/canvas-management/canvas-statuses', data: planData }) + .then((res) => { + swalFire({ text: getMessage('plan.message.save') }) + setInitCanvasPlans((initCanvasPlans) => + initCanvasPlans.map((plan) => (plan.id === currentCanvasPlan.id ? { ...plan, canvasStatus: canvasStatus } : plan)), + ) + setPlans((plans) => plans.map((plan) => (plan.id === currentCanvasPlan.id ? { ...plan, canvasStatus: canvasStatus } : plan))) + }) + .catch((error) => { + swalFire({ text: error.message, icon: 'error' }) + }) + } + + /** + * id에 해당하는 canvas 데이터를 삭제 */ const delCanvasById = (id) => { return promiseDel({ url: `/api/canvas-management/canvas-statuses/by-id/${id}` }) } /** - * objectNo에 해당하는 canvas 데이터들을 삭제하는 함수 + * objectNo에 해당하는 canvas 데이터들을 삭제 */ const delCanvasByObjectNo = (objectNo) => { return promiseDel({ url: `/api/canvas-management/canvas-statuses/by-object/${objectNo}` }) } + /** + * plan 이동 + * 현재 plan의 작업상태를 확인, 저장 후 이동 + */ + const handleCurrentPlan = (userId, newCurrentId) => { + if (!currentCanvasPlan || currentCanvasPlan.id !== newCurrentId) { + if (currentCanvasPlan?.id && checkModifiedCanvasPlan()) { + swalFire({ + text: `${currentCanvasPlan.name} ` + getMessage('plan.message.confirm.save'), + type: 'confirm', + confirmFn: async () => { + await saveCanvas(userId) + updateCurrentPlan(newCurrentId) + }, + denyFn: () => { + updateCurrentPlan(newCurrentId) + }, + }) + } else { + updateCurrentPlan(newCurrentId) + } + } + } + const updateCurrentPlan = (newCurrentId) => { + setPlans((plans) => + plans.map((plan) => { + return { ...plan, isCurrent: plan.id === newCurrentId } + }), + ) + } + useEffect(() => { + setCurrentCanvasPlan(plans.find((plan) => plan.isCurrent) || null) + }, [plans]) + + /** + * 새로운 plan 생성 + * 현재 plan의 데이터가 있을 경우 복제 여부를 확인 + */ + const handleAddPlan = (userId, objectNo) => { + JSON.parse(currentCanvasData()).objects.length > 0 + ? swalFire({ + text: `${currentCanvasPlan.name} ` + getMessage('plan.message.confirm.copy'), + type: 'confirm', + confirmFn: () => { + addPlan(userId, objectNo, currentCanvasData()) + }, + denyFn: () => { + addPlan(userId, objectNo) + }, + }) + : addPlan(userId, objectNo) + } + const addPlan = (userId, objectNo, canvasStatus = '') => { + const newPlan = { + id: planNum, + name: `Plan ${planNum + 1}`, + objectNo: objectNo, + userId: userId, + canvasStatus: canvasStatus, + } + setPlans([...plans, newPlan]) + handleCurrentPlan(userId, planNum) + setPlanNum(planNum + 1) + } + + /** + * plan 삭제 + */ + const handleDeletePlan = (e, id) => { + e.stopPropagation() // 이벤트 버블링 방지 + + if (initCanvasPlans.some((plan) => plan.id === id)) { + delCanvasById(id) + .then((res) => { + swalFire({ text: getMessage('plan.message.delete') }) + setInitCanvasPlans((initCanvasPlans) => initCanvasPlans.filter((plan) => plan.id !== id)) + setPlans((plans) => plans.filter((plan) => plan.id !== id)) + }) + .catch((error) => { + swalFire({ text: error.message, icon: 'error' }) + }) + } else { + setPlans((plans) => plans.filter((plan) => plan.id !== id)) + swalFire({ text: getMessage('plan.message.delete') }) + } + + // 삭제 후 last 데이터에 포커싱 + const lastPlan = plans.filter((plan) => plan.id !== id).at(-1) + if (!lastPlan) { + setPlanNum(0) + setCurrentCanvasPlan(null) + } else if (id !== lastPlan.id) { + updateCurrentPlan(lastPlan.id) + } + } + + /** + * plan 조회 + */ + const loadCanvasPlanData = (userId, objectNo) => { + getCanvasByObjectNo(userId, objectNo).then((res) => { + // console.log('canvas 목록 ', res) + if (res.length > 0) { + setInitCanvasPlans(res) + setPlans(res) + updateCurrentPlan(res.at(-1).id) // last 데이터에 포커싱 + setPlanNum(res.length) + } else { + addPlan(userId, objectNo) + } + }) + } + return { canvas, - removeMouseLines, + plans, saveCanvas, - addCanvas, - checkModifiedCanvasPlan, - getCanvasByObjectNo, - delCanvasById, + handleCurrentPlan, + handleAddPlan, + handleDeletePlan, + loadCanvasPlanData, } } diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 2d981933..7289a0d3 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -3,6 +3,7 @@ import { useRecoilValue } from 'recoil' import { fabric } from 'fabric' import { getDirectionByPoint } from '@/util/canvas-util' import { QPolygon } from '@/components/fabric/QPolygon' +import { isSamePoint } from '@/util/qpolygon-utils' export const usePolygon = () => { const canvas = useRecoilValue(canvasState) @@ -25,6 +26,8 @@ export const usePolygon = () => { } const addPolygonByLines = (lines, options) => { + //lines의 idx를 정렬한다. + lines.sort((a, b) => a.idx - b.idx) const points = createPolygonPointsFromOuterLines(lines) return addPolygon(points, { diff --git a/src/lib/authActions.js b/src/lib/authActions.js index d3f5aaf3..618985b9 100644 --- a/src/lib/authActions.js +++ b/src/lib/authActions.js @@ -49,6 +49,8 @@ export async function setSession(data) { session.telNo = data.telNo session.fax = data.fax session.email = data.email + session.storeLvl = data.storeLvl + session.groupId = data.groupId session.pwdInitYn = data.pwdInitYn session.isLoggedIn = true // console.log('session:', session) diff --git a/src/locales/ja.json b/src/locales/ja.json index f4f1e299..0545be6e 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -109,10 +109,37 @@ "modal.module.basic.setting.module.placement.arrangement.standard.eaves": "軒側", "modal.module.basic.setting.module.placement.arrangement.standard.ridge": "龍丸側", "modal.module.basic.setting.module.placement.maximum": "最大配置する。", + "modal.module.basic.setting.pitch.module.placement.standard.setting": "配置基準の設定", + "modal.module.basic.setting.pitch.module.placement.standard.setting.south": "南向きに設置す", + "modal.module.basic.setting.pitch.module.placement.standard.setting.select": "指定した辺を基準に設置する", + "modal.module.basic.setting.pitch.module.allocation.setting": "割り当て設定", + "modal.module.basic.setting.pitch.module.allocation.setting.info": "※バッチパネルの種類が1種類の場合にのみ使用できます。", + "modal.module.basic.setting.pitch.module.row.amount": "単数", + "modal.module.basic.setting.pitch.module.row.margin": "上下間隔", + "modal.module.basic.setting.pitch.module.column.amount": "熱数", + "modal.module.basic.setting.pitch.module.column.margin": "左右間隔", "modal.module.basic.setting.prev": "以前", "modal.module.basic.setting.passivity.placement": "手動配置", "modal.module.basic.setting.auto.placement": "設定値に自動配置", "plan.menu.module.circuit.setting.circuit.trestle.setting": "回路と架台の設定", + "modal.circuit.trestle.setting": "回路と架台設定", + "modal.circuit.trestle.setting.power.conditional.select": "パワーコンディショナーを選択", + "modal.circuit.trestle.setting.power.conditional.select.name": "名称", + "modal.circuit.trestle.setting.power.conditional.select.rated.output": "定格出力", + "modal.circuit.trestle.setting.power.conditional.select.circuit.amount": "回路数", + "modal.circuit.trestle.setting.power.conditional.select.max.connection": "最大接続枚数", + "modal.circuit.trestle.setting.power.conditional.select.max.overload": "過積最大枚数", + "modal.circuit.trestle.setting.power.conditional.select.output.current": "出力電流", + "modal.circuit.trestle.setting.circuit.allocation": "回路割り当て", + "modal.circuit.trestle.setting.circuit.allocation.auto": "自動回路割り当て", + "modal.circuit.trestle.setting.circuit.allocation.passivity": "手動回路割当", + "modal.circuit.trestle.setting.circuit.allocation.passivity.info": "同じ回路のモジュールを選択状態にした後、 [番号確認]ボタンを押すと番号が割り当てられます。", + "modal.circuit.trestle.setting.circuit.allocation.passivity.selected.power.conditional": "選択したパワーコンディショナー", + "modal.circuit.trestle.setting.circuit.allocation.passivity.circuit.num": "設定する回路番号 (1~)", + "modal.circuit.trestle.setting.circuit.allocation.passivity.selected.power.conditional.reset": "選択されたパワーコンディショナーの回路番号の初期化", + "modal.circuit.trestle.setting.circuit.allocation.passivity.all.power.conditional.reset": "すべての回路番号の初期化", + "modal.circuit.trestle.setting.circuit.allocation.passivity.circuit.num.fix": "番号確定", + "modal.circuit.trestle.setting.step.up.allocation": "昇圧設定", "plan.menu.module.circuit.setting.plan.orientation": "図面方位の適用", "plan.menu.estimate": "見積", "plan.menu.estimate.roof.alloc": "屋根面の割り当て", @@ -158,6 +185,7 @@ "modal.grid.copy.save": "保存", "modal.common.save": "保存", "modal.common.add": "追加", + "modal.common.prev": "以前", "modal.canvas.setting.font.plan.edit": "フォントとサイズの変更", "modal.canvas.setting.font.plan.edit.word": "文字フォントの変更", "modal.canvas.setting.font.plan.edit.flow": "フロー方向フォントの変更", @@ -219,6 +247,11 @@ "modal.object.setting.direction.select": "方向の選択", "modal.placement.surface.setting.info": "ⓘ ①の長さ入力後に対角線の長さを入力すると、②の長さを自動計算します。", "modal.placement.surface.setting.diagonal.length": "斜めの長さ", + "plan.message.confirm.save": "PLAN을 저장하시겠습니까?", + "plan.message.confirm.copy": "PLAN을 복사하시겠습니까?", + "plan.message.confirm.delete": "PLAN을 삭제하시겠습니까?", + "plan.message.save": "저장되었습니다.", + "plan.message.delete": "삭제되었습니다.", "setting": "設定", "common.message.no.data": "No data", "common.message.no.dataDown": "ダウンロードするデータがありません", @@ -307,59 +340,96 @@ "common.message.writeToConfirm": "作成解除を実行しますか?", "common.message.password.init.success": "パスワード [{0}] に初期化されました。", "common.message.no.edit.save": "この文書は変更できません。", - "common.require": "필수", + "common.require": "必須", "commons.west": "立つ", "commons.east": "ドン", "commons.south": "立つ", "commons.north": "北", "site.name": "Q.CAST III", - "site.sub_name": "태양광 발전 시스템 도면관리 사이트", - "login": "로그인", - "login.init_password.btn": "비밀번호 초기화 ja", - "login.init_password.title": "비밀번호 초기화", - "login.init_password.sub_title": "비밀번호를 초기화할 아이디와 이메일 주소를 입력해 주세요.", - "login.init_password.complete_message": "비밀번호가 초기화 되었습니다. 초기화된 비밀번호는 아이디와 같습니다.", - "join.title": "Q.CAST3 로그인ID 발행 신청", - "join.sub1.title": "판매대리점 정보", - "join.sub1.comment": "※ 등록되는 리셀러의 회사 이름을 입력하십시오. (2차점은 「○○판매주식회사(2차점:××설비주식회사)」로 기입해 주세요.)", - "join.sub1.storeQcastNm": "판매대리점명", - "join.sub1.storeQcastNm_placeholder": "株式会社エネルギア・ソリューション・アンド・サービス(2次店:山口住機販売有限会社)", - "join.sub1.storeQcastNmKana": "판매대리점명 후리가나", - "join.sub1.storeQcastNmKana_placeholder": "カブシキガイシャエネルギア・ソリューション・アン", - "join.sub1.postCd": "우편번호", - "join.sub1.postCd_placeholder": "숫자 7자리", - "join.sub1.addr": "주소", - "join.sub1.addr_placeholder": "전각50자이내", - "join.sub1.telNo": "전화번호", + "site.sub_name": "太陽光発電システム図面管理サイト", + "board.notice.title": "お知らせ", + "board.notice.sub.title": "お知らせ一覧", + "board.faq.title": "FAQ", + "board.faq.sub.title": "FAQ 一覧", + "board.archive.title": "資料ダウンロード", + "board.archive.sub.title": "文書一覧", + "board.list.header.rownum": "番号", + "board.list.header.title": "タイトル", + "board.list.header.regDt": "登録日", + "board.sub.search.placeholder": "検索語を入力してください。", + "board.sub.search.result": "{0}について、合計{1}件の投稿が検索されました。", + "board.sub.search.archive.result": "{0}について、合計{1}件の文書が検索されました。", + "board.sub.total": "全体", + "board.sub.fileList": "添付ファイル一覧", + "board.sub.updDt": "更新日", + "myinfo.title": "マイプロフィール", + "myinfo.info.userId": "ユーザーID", + "myinfo.info.nameKana": "担当者名ふりがな", + "myinfo.info.name": "担当者名", + "myinfo.info.password": "パスワード", + "myinfo.info.chg.password": "新しいパスワード入力", + "myinfo.info.category": "部署名", + "myinfo.info.tel": "電話番号", + "myinfo.info.fax": "FAX番号", + "myinfo.info.mail": "メールアドレス", + "myinfo.sub.validation.password": "※ 半角10文字以内", + "myinfo.btn.close": "閉じる", + "myinfo.btn.chg.password": "パスワード変更", + "myinfo.btn.chg": "変更", + "myinfo.btn.noChg": "変更しない", + "myinfo.btn.confirm": "確認", + "myinfo.message.validation.password1": "パスワードを入力してください。", + "myinfo.message.validation.password2": "既存のパスワードと同じです。", + "myinfo.message.validation.password3": "半角文字10文字以内で入力してください。", + "myinfo.message.validation.password4": "変更パスワードを入力してください。", + "myinfo.message.save": "パスワードが変更されました。", + "myinfo.message.password.error": "パスワードが間違っています。", + "login": "ログイン", + "login.id.save": "ID保存", + "login.id.placeholder": "IDを入力してください。", + "login.password.placeholder": "パスワードを入力してください。", + "login.guide.text": "当サイトを利用するには、事前申請が必要です。", + "login.guide.sub1": "IDをお持ちでない方は", + "login.guide.sub2": "をクリックしてください。", + "login.guide.join.btn": "ID申請", + "login.init_password.btn": "パスワードリセット", + "login.init_password.btn.back": "前の画面に戻る", + "login.init_password.title": "パスワードリセット", + "login.init_password.sub_title": "パスワードをリセットするIDとメールアドレスを入力してください。", + "login.init_password.complete_message": "パスワードがリセットされました。リセット後のパスワードはIDと同じです。", + "login.init_password.id.placeholder": "IDを入力してください。", + "login.init_password.email.placeholder": "メールアドレスを入力してください。", + "join.title": "Q.CAST3 ログインID発行申請", + "join.sub1.title": "販売代理店情報", + "join.sub1.comment": "※ 登録する販売店の会社名を入力してください。(2次店の場合「○○販売株式会社(2次店:××設備株式会社)」と記載してください。)", + "join.sub1.storeQcastNm": "販売代理店名", + "join.sub1.storeQcastNm_placeholder": "株式会社 エナジー ギア ソリューション アンド サービス(2次店: 山口重機販売有限会社)", + "join.sub1.storeQcastNmKana": "販売代理店名ふりがな", + "join.sub1.storeQcastNmKana_placeholder": "株式会社 エナジー ギア ソリューション", + "join.sub1.postCd": "郵便番号", + "join.sub1.postCd_placeholder": "7桁の数字", + "join.sub1.addr": "住所", + "join.sub1.addr_placeholder": "全角50文字以内", + "join.sub1.telNo": "電話番号", "join.sub1.telNo_placeholder": "00-0000-0000", - "join.sub1.fax": "FAX 번호", + "join.sub1.fax": "FAX番号", "join.sub1.fax_placeholder": "00-0000-0000", - "join.sub2.title": "담당자 정보", - "join.sub2.userNm": "담당자명", - "join.sub2.userNmKana": "담당자명 후리가나", - "join.sub2.userId": "신청 ID", - "join.sub2.email": "이메일 주소", - "join.sub2.telNo": "전화번호", + "join.sub1.bizNo": "法人番号", + "join.sub2.title": "担当者情報", + "join.sub2.userNm": "担当者名", + "join.sub2.userNmKana": "担当者名ふりがな", + "join.sub2.userId": "申請ID", + "join.sub2.email": "メールアドレス", + "join.sub2.telNo": "電話番号", "join.sub2.telNo_placeholder": "00-0000-0000", - "join.sub2.fax": "FAX 번호", + "join.sub2.fax": "FAX番号", "join.sub2.fax_placeholder": "00-0000-0000", - "join.sub2.category": "부서명", - "join.sub3.title": "견적서 제출용 회사정보", - "join.sub3.qtCompNm": "회사명", - "join.sub3.qtPostCd": "우편번호", - "join.sub3.qtPostCd_placeholder": "숫자 7자리", - "join.sub3.qtAddr": "주소", - "join.sub3.qtAddr_placeholder": "전각50자이내", - "join.sub3.qtEmail": "이메일 주소", - "join.sub3.qtTelNo": "전화번호", - "join.sub3.qtTelNo_placeholder": "00-0000-0000", - "join.sub3.qtFax": "FAX 번호", - "join.sub3.qtFax_placeholder": "00-0000-0000", - "join.btn.approval_request": "ID 승인요청", - "join.complete.title": "Q.CAST3 로그인ID 발행신청 완료", - "join.complete.contents": "※ 신청한 ID가 승인되면, 담당자 정보에 입력한 이메일 주소로 로그인 관련 안내 메일이 전송됩니다.", - "join.complete.email_comment": "담당자 이메일 주소", - "join.complete.email": "test@naver.com", + "join.sub2.category": "部署名", + "join.btn.login_page": "ログイン画面に移動", + "join.btn.approval_request": "ID承認申請", + "join.complete.title": "Q.CAST3 ログインID発行申請完了", + "join.complete.contents": "※ 申請したIDが承認されると、担当者情報に入力されたメールアドレスにログイン案内メールが送信されます。", + "join.complete.email_comment": "担当者メールアドレス", "stuff.gridHeader.lastEditDatetime": "更新日時", "stuff.gridHeader.objectNo": "品番", "stuff.gridHeader.planTotCnt": "プラン数", @@ -372,6 +442,94 @@ "stuff.gridHeader.specDate": "仕様確認日", "stuff.gridHeader.createDatetime": "登録日", "stuff.message.periodError": "最大1年間閲覧可能.", + "stuff.addressPopup.title": "郵便番号", + "stuff.addressPopup.placeholder": "郵便番号の7桁を入力してください。", + "stuff.addressPopup.error.message1": "登録された郵便番号に住所が見つかりません。もう一度入力してください。", + "stuff.addressPopup.error.message2": "住所を選択してください.", + "stuff.addressPopup.gridHeader.address1": "都道府県", + "stuff.addressPopup.gridHeader.address2": "市区町村", + "stuff.addressPopup.gridHeader.address3": "市区町村 以下", + "stuff.addressPopup.btn1": "閉じる", + "stuff.addressPopup.btn2": "住所適用", + "stuff.planReqPopup.title": "設計依頼のインポート", + "stuff.detail.required": "必須入力項目", + "stuff.detail.planReqNo": "設計依頼No.", + "stuff.detail.dispCompanyName": "担当者", + "stuff.detail.objectStatusId": "物品区分/物件名", + "stuff.detail.objectStatus0": "新築", + "stuff.detail.objectStatus1": "基軸", + "stuff.detail.objectNameKana": "商品名 ふりがな", + "stuff.detail.saleStoreId": "一次販売店名/ID", + "stuff.detail.otherSaleStoreId": "二次販売店名/ID", + "stuff.detail.zipNo": "郵便番号 ", + "stuff.detail.btn.addressPop": "住所検索", + "stuff.detail.btn.addressPop.guide": "※ 郵便番号7桁を入力した後、アドレス検索ボタンをクリックしてください", + "stuff.detail.prefId": "都道府県 / 住所 ", + "stuff.detail.areaId": "発電量シミュレーション地域 ", + "stuff.detail.windSpeed": "基準風速", + "stuff.detail.windSpeedSpan": "m/s以下", + "stuff.detail.btn.windSpeedPop": "風速選択", + "stuff.detail.verticalSnowCover": "垂直説説", + "stuff.detail.coldRegionFlg": "寒冷地対策施行", + "stuff.detail.surfaceType": "面調図区分", + "stuff.detail.saltAreaFlg": "塩害地域用アイテムの使用", + "stuff.detail.installHeight": "設置高さ", + "stuff.detail.conType": "契約条件", + "stuff.detail.conType0": "余剰", + "stuff.detail.conType1": "全量", + "stuff.detail.remarks": "メモ", + "stuff.detail.tooltip.saleStoreId": "販売代理店または販売代理店IDを1文字以上入力してください", + "stuff.planReqPopup.popTitle": "設計依頼検索", + "stuff.planReqPopup.btn1": "検索", + "stuff.planReqPopup.btn2": "初期化", + "stuff.planReqPopup.btn3": "閉じる", + "stuff.planReqPopup.btn4": "選択の適用", + "stuff.planReqPopup.gridHeader.planStatName": "상태", + "stuff.planReqPopup.gridHeader.planReqNo": "설계의뢰 번호", + "stuff.planReqPopup.gridHeader.saleStoreId": "판매대리점ID", + "stuff.planReqPopup.gridHeader.saleStoreName": "판매대리점명", + "stuff.planReqPopup.gridHeader.title": "안건명", + "stuff.planReqPopup.gridHeader.address1": "도도부현", + "stuff.planReqPopup.gridHeader.houseCntName": "설치가옥수", + "stuff.planReqPopup.gridHeader.planReqName": "의뢰자명", + "stuff.planReqPopup.gridHeader.submitDt": "설계의뢰 제출일", + "stuff.planReqPopup.search.planReqNo": "設計依頼番号", + "stuff.planReqPopup.search.title": "案件名", + "stuff.planReqPopup.search.address1": "都道府県", + "stuff.planReqPopup.search.saleStoreName": "販売代理店名", + "stuff.planReqPopup.search.planReqName": "依頼者名", + "stuff.planReqPopup.search.planStatName": "状態", + "stuff.planReqPopup.search.period": "期間検索", + "stuff.planReqPopup.search.schDateGbnS": "提出日", + "stuff.planReqPopup.search.schDateGbnR": "受付日", + "stuff.planReqPopup.error.message1": "設計依頼を選択してください。", + "stuff.search.title": "物件状況", + "stuff.search.btn1": "物件登録", + "stuff.search.btn2": "照会", + "stuff.search.btn3": "初期化", + "stuff.search.schObjectNo": "品番", + "stuff.search.schSaleStoreName": "販売代理店名", + "stuff.search.schAddress": "商品アドレス", + "stuff.search.schObjectName": "商品名", + "stuff.search.schDispCompanyName": "見積もり", + "stuff.search.schSelSaleStoreId": "販売代理店の選択", + "stuff.search.schReceiveUser": "担当者", + "stuff.search.period": "期間検索", + "stuff.search.schDateTypeU": "更新日", + "stuff.search.schDateTypeR": "登録日", + "stuff.search.grid.title": "商品リスト", + "stuff.search.grid.all": "全体", + "stuff.search.grid.selected": "選択", + "stuff.search.grid.schSortTypeR": "最近の登録日", + "stuff.search.grid.schSortTypeU": "最近の更新日", + "stuff.windSelectPopup.title": "風速選択", + "stuff.windSelectPopup.table.selected": "選択", + "stuff.windSelectPopup.table.windspeed": "風速", + "stuff.windSelectPopup.error.message1": "住所を先に検索してください", + "stuff.windSelectPopup.error.message2": "風速を選択してください。", + "stuff.windSelectPopup.search.address1": "県", + "stuff.windSelectPopup.btn1": "閉じる", + "stuff.windSelectPopup.btn2": "選択", "length": "長さ", "height": "高さ", "output": "出力", @@ -425,7 +583,7 @@ "main.popup.login.validate1": "入力したパスワードが異なります。", "main.popup.login.validate2": "半角10文字以内で入力してください。", "main.popup.login.success": "パスワードが変更されました。", - "surface.shape.validate.size": "寸法を入力してください.", + "common.canvas.validate.size": "寸法を入力してください.", "surface.shape.validate.size.1to2": "①길이는 ②보다 큰 값을 넣어주세요.", "surface.shape.validate.size.1to3": "①길이는 ③보다 큰 값을 넣어주세요.", "surface.shape.validate.size.1to23": "①길이는 ②+③보다 큰 값을 넣어주세요.", diff --git a/src/locales/ko.json b/src/locales/ko.json index 9d518a5e..d02eed19 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -113,10 +113,37 @@ "modal.module.basic.setting.module.placement.arrangement.standard.eaves": "처마쪽", "modal.module.basic.setting.module.placement.arrangement.standard.ridge": "용마루쪽", "modal.module.basic.setting.module.placement.maximum": "최대배치 실시한다.", + "modal.module.basic.setting.pitch.module.placement.standard.setting": "배치 기준 설정", + "modal.module.basic.setting.pitch.module.placement.standard.setting.south": "남향으로 설치한다", + "modal.module.basic.setting.pitch.module.placement.standard.setting.select": "지정한 변을 기준으로 설치한다", + "modal.module.basic.setting.pitch.module.allocation.setting": "할당 설정", + "modal.module.basic.setting.pitch.module.allocation.setting.info": "※배치 패널 종류가 1종류일 경우에만 사용할 수 있습니다.", + "modal.module.basic.setting.pitch.module.row.amount": "단수", + "modal.module.basic.setting.pitch.module.row.margin": "상하간격", + "modal.module.basic.setting.pitch.module.column.amount": "열수", + "modal.module.basic.setting.pitch.module.column.margin": "좌우간격", "modal.module.basic.setting.prev": "이전", "modal.module.basic.setting.passivity.placement": "수동 배치", "modal.module.basic.setting.auto.placement": "설정값으로 자동 배치", "plan.menu.module.circuit.setting.circuit.trestle.setting": "회로 및 가대 설정", + "modal.circuit.trestle.setting": "회로 및 가대설정", + "modal.circuit.trestle.setting.power.conditional.select": "파워컨디셔너 선택", + "modal.circuit.trestle.setting.power.conditional.select.name": "명칭", + "modal.circuit.trestle.setting.power.conditional.select.rated.output": "정격출력", + "modal.circuit.trestle.setting.power.conditional.select.circuit.amount": "회로수", + "modal.circuit.trestle.setting.power.conditional.select.max.connection": "최대접속매수", + "modal.circuit.trestle.setting.power.conditional.select.max.overload": "과적최대매수", + "modal.circuit.trestle.setting.power.conditional.select.output.current": "출력전류", + "modal.circuit.trestle.setting.circuit.allocation": "회로 할당", + "modal.circuit.trestle.setting.circuit.allocation.auto": "자동 회로 할당", + "modal.circuit.trestle.setting.circuit.allocation.passivity": "수동 회로 할당", + "modal.circuit.trestle.setting.circuit.allocation.passivity.info": "동일한 회로의 모듈을 선택 상태로 만든 후 [번호 확정] 버튼을 누르면 번호가 할당됩니다.", + "modal.circuit.trestle.setting.circuit.allocation.passivity.selected.power.conditional": "선택된 파워컨디셔너", + "modal.circuit.trestle.setting.circuit.allocation.passivity.circuit.num": "설정할 회로번호(1~)", + "modal.circuit.trestle.setting.circuit.allocation.passivity.selected.power.conditional.reset": "선택된 파워컨디셔너의 회로번호 초기화", + "modal.circuit.trestle.setting.circuit.allocation.passivity.all.power.conditional.reset": "모든 회로번호 초기화", + "modal.circuit.trestle.setting.circuit.allocation.passivity.circuit.num.fix": "번호 확정", + "modal.circuit.trestle.setting.step.up.allocation": "승압 설정", "plan.menu.module.circuit.setting.plan.orientation": "도면 방위 적용", "plan.menu.estimate": "견적서", "plan.menu.estimate.roof.alloc": "지붕면 할당", @@ -162,6 +189,8 @@ "modal.grid.copy.save": "저장", "modal.common.save": "저장", "modal.common.add": "추가", + "modal.common.prev": "이전", + "modal.common.next": "다음", "modal.canvas.setting.font.plan.edit": "글꼴 및 크기 변경", "modal.canvas.setting.font.plan.edit.word": "문자 글꼴 변경", "modal.canvas.setting.font.plan.edit.flow": "흐름 방향 글꼴 변경", @@ -223,6 +252,11 @@ "modal.object.setting.direction.select": "방향 선택", "modal.placement.surface.setting.info": "ⓘ ①의 길이 입력 후 대각선 길이를 입력하면 ②의 길이를 자동 계산합니다.", "modal.placement.surface.setting.diagonal.length": "대각선 길이", + "plan.message.confirm.save": "PLAN을 저장하시겠습니까?", + "plan.message.confirm.copy": "PLAN을 복사하시겠습니까?", + "plan.message.confirm.delete": "PLAN을 삭제하시겠습니까?", + "plan.message.save": "저장되었습니다.", + "plan.message.delete": "삭제되었습니다.", "setting": "설정", "common.message.no.data": "No data", "common.message.no.dataDown": "No data to download", @@ -318,11 +352,58 @@ "commons.north": "북", "site.name": "Q.CAST III", "site.sub_name": "태양광 발전 시스템 도면관리 사이트", + "board.notice.title": "공지사항", + "board.notice.sub.title": "공지사항 목록", + "board.faq.title": "FAQ", + "board.faq.sub.title": "FAQ 목록", + "board.archive.title": "자료 다운로드", + "board.archive.sub.title": "문서 목록", + "board.list.header.rownum": "번호", + "board.list.header.title": "제목", + "board.list.header.regDt": "등록일", + "board.sub.search.placeholder": "검색어를 입력하세요.", + "board.sub.search.result": "{0}에 대해 총 {1}건의 글이 검색 되었습니다.", + "board.sub.search.archive.result": "{0}에 대해 총 {1}건의 문서가 검색 되었습니다.", + "board.sub.total": "전체", + "board.sub.fileList": "첨부파일 목록", + "board.sub.updDt": "업데이트", + "myinfo.title": "My profile", + "myinfo.info.userId": "사용자ID", + "myinfo.info.nameKana": "담당자명 후리가나", + "myinfo.info.name": "담당자명", + "myinfo.info.password": "비밀번호", + "myinfo.info.chg.password": "변경 비밀번호 입력", + "myinfo.info.category": "부서명", + "myinfo.info.tel": "전화번호", + "myinfo.info.fax": "FAX번호", + "myinfo.info.mail": "이메일 주소", + "myinfo.sub.validation.password": "※ 반각10자 이내", + "myinfo.btn.close": "닫기", + "myinfo.btn.chg.password": "비밀번호 변경", + "myinfo.btn.chg": "변경", + "myinfo.btn.noChg": "변경안함", + "myinfo.btn.confirm": "확인", + "myinfo.message.validation.password1": "비밀번호를 입력하세요.", + "myinfo.message.validation.password2": "기존 비밀번호와 동일합니다.", + "myinfo.message.validation.password3": "반각 문자 10자이내여야 합니다.", + "myinfo.message.validation.password4": "변경 비밀번호를 입력하세요.", + "myinfo.message.save": "비밀번호가 변경되었습니다.", + "myinfo.message.password.error": "비밀번호가 틀렸습니다.", "login": "로그인", + "login.id.save": "ID Save", + "login.id.placeholder": "아이디를 입력해주세요.", + "login.password.placeholder": "비밀번호를 입력해주세요.", + "login.guide.text": "당 사이트를 이용할 때는, 사전 신청이 필요합니다.", + "login.guide.sub1": "ID가 없는 분은", + "login.guide.sub2": "을 클릭해주십시오.", + "login.guide.join.btn": "ID신청", "login.init_password.btn": "비밀번호 초기화", + "login.init_password.btn.back": "이전 화면으로", "login.init_password.title": "비밀번호 초기화", "login.init_password.sub_title": "비밀번호를 초기화할 아이디와 이메일 주소를 입력해 주세요.", "login.init_password.complete_message": "비밀번호가 초기화 되었습니다. 초기화된 비밀번호는 아이디와 같습니다.", + "login.init_password.id.placeholder": "ID를 입력하세요.", + "login.init_password.email.placeholder": "이메일을 입력하세요.", "join.title": "Q.CAST3 로그인ID 발행 신청", "join.sub1.title": "판매대리점 정보", "join.sub1.comment": "※ 등록되는 리셀러의 회사 이름을 입력하십시오. (2차점은 「○○판매주식회사(2차점:××설비주식회사)」로 기입해 주세요.)", @@ -338,6 +419,7 @@ "join.sub1.telNo_placeholder": "00-0000-0000", "join.sub1.fax": "FAX 번호", "join.sub1.fax_placeholder": "00-0000-0000", + "join.sub1.bizNo": "법인번호", "join.sub2.title": "담당자 정보", "join.sub2.userNm": "담당자명", "join.sub2.userNmKana": "담당자명 후리가나", @@ -348,22 +430,11 @@ "join.sub2.fax": "FAX 번호", "join.sub2.fax_placeholder": "00-0000-0000", "join.sub2.category": "부서명", - "join.sub3.title": "견적서 제출용 회사정보", - "join.sub3.qtCompNm": "회사명", - "join.sub3.qtPostCd": "우편번호", - "join.sub3.qtPostCd_placeholder": "숫자 7자리", - "join.sub3.qtAddr": "주소", - "join.sub3.qtAddr_placeholder": "전각50자이내", - "join.sub3.qtEmail": "이메일 주소", - "join.sub3.qtTelNo": "전화번호", - "join.sub3.qtTelNo_placeholder": "00-0000-0000", - "join.sub3.qtFax": "FAX 번호", - "join.sub3.qtFax_placeholder": "00-0000-0000", + "join.btn.login_page": "로그인 화면으로 이동", "join.btn.approval_request": "ID 승인요청", "join.complete.title": "Q.CAST3 로그인ID 발행신청 완료", "join.complete.contents": "※ 신청한 ID가 승인되면, 담당자 정보에 입력한 이메일 주소로 로그인 관련 안내 메일이 전송됩니다.", "join.complete.email_comment": "담당자 이메일 주소", - "join.complete.email": "test@naver.com", "stuff.gridHeader.lastEditDatetime": "갱신일시", "stuff.gridHeader.objectNo": "물건번호", "stuff.gridHeader.planTotCnt": "플랜 수", @@ -376,6 +447,94 @@ "stuff.gridHeader.specDate": "사양확인일", "stuff.gridHeader.createDatetime": "등록일", "stuff.message.periodError": "최대1년 조회 가능합니다.", + "stuff.addressPopup.title": "우편번호", + "stuff.addressPopup.placeholder": "우편번호의 7자리를 입력하세요.", + "stuff.addressPopup.error.message1": "등록된 우편번호에서 주소를 찾을 수 없습니다. 다시 입력해주세요.", + "stuff.addressPopup.error.message2": "주소를 선택해주세요.", + "stuff.addressPopup.gridHeader.address1": "도도부현", + "stuff.addressPopup.gridHeader.address2": "시구정촌", + "stuff.addressPopup.gridHeader.address3": "시구정촌 이하", + "stuff.addressPopup.btn1": "닫기", + "stuff.addressPopup.btn2": "주소적용", + "stuff.planReqPopup.title": "설계의뢰 불러오기", + "stuff.detail.required": "필수 입력항목", + "stuff.detail.planReqNo": "설계의뢰No.", + "stuff.detail.dispCompanyName": "담당자", + "stuff.detail.objectStatusId": "물건구분/물건명", + "stuff.detail.objectStatus0": "신축", + "stuff.detail.objectStatus1": "기축", + "stuff.detail.objectNameKana": "물건명 후리가나", + "stuff.detail.saleStoreId": "1차 판매점명 / ID", + "stuff.detail.otherSaleStoreId": "2차 판매점명 / ID", + "stuff.detail.zipNo": "우편번호", + "stuff.detail.btn.addressPop": "주소검색", + "stuff.detail.btn.addressPop.guide": "※ 주소검색 버튼을 클릭한 후, 도도부현 정보를 선택해주십시오.", + "stuff.detail.prefId": "도도부현 / 주소", + "stuff.detail.areaId": "발전량시뮬레이션지역", + "stuff.detail.windSpeed": "기준풍속", + "stuff.detail.windSpeedSpan": "m/s이하", + "stuff.detail.btn.windSpeedPop": "풍속선택", + "stuff.detail.verticalSnowCover": "수직적설량", + "stuff.detail.coldRegionFlg": "한랭지대책시행", + "stuff.detail.surfaceType": "면조도구분", + "stuff.detail.saltAreaFlg": "염해지역용아이템사용", + "stuff.detail.installHeight": "설치높이", + "stuff.detail.conType": "계약조건", + "stuff.detail.conType0": "잉여", + "stuff.detail.conType1": "전량", + "stuff.detail.remarks": "메모", + "stuff.detail.tooltip.saleStoreId": "판매대리점 또는 판매대리점ID를 1자 이상 입력하세요", + "stuff.planReqPopup.popTitle": "설계 요청 검색", + "stuff.planReqPopup.btn1": "검색", + "stuff.planReqPopup.btn2": "초기화", + "stuff.planReqPopup.btn3": "닫기", + "stuff.planReqPopup.btn4": "선택적용", + "stuff.planReqPopup.gridHeader.planStatName": "상태", + "stuff.planReqPopup.gridHeader.planReqNo": "설계의뢰 번호", + "stuff.planReqPopup.gridHeader.saleStoreId": "판매대리점ID", + "stuff.planReqPopup.gridHeader.saleStoreName": "판매대리점명", + "stuff.planReqPopup.gridHeader.title": "안건명", + "stuff.planReqPopup.gridHeader.address1": "도도부현", + "stuff.planReqPopup.gridHeader.houseCntName": "설치가옥수", + "stuff.planReqPopup.gridHeader.planReqName": "의뢰자명", + "stuff.planReqPopup.gridHeader.submitDt": "설계의뢰 제출일", + "stuff.planReqPopup.search.planReqNo": "설계의뢰번호", + "stuff.planReqPopup.search.title": "안건명", + "stuff.planReqPopup.search.address1": "도도부현", + "stuff.planReqPopup.search.saleStoreName": "판매대리점명", + "stuff.planReqPopup.search.planReqName": "의뢰자명", + "stuff.planReqPopup.search.planStatName": "상태", + "stuff.planReqPopup.search.period": "기간검색", + "stuff.planReqPopup.search.schDateGbnS": "제출일", + "stuff.planReqPopup.search.schDateGbnR": "접수일", + "stuff.planReqPopup.error.message1": "설계의뢰를 선택해주세요.", + "stuff.search.title": "물건현황", + "stuff.search.btn1": "신규등록", + "stuff.search.btn2": "조회", + "stuff.search.btn3": "초기화", + "stuff.search.schObjectNo": "물건번호", + "stuff.search.schSaleStoreName": "판매대리점명", + "stuff.search.schAddress": "물건주소", + "stuff.search.schObjectName": "물건명", + "stuff.search.schDispCompanyName": "견적처", + "stuff.search.schSelSaleStoreId": "판매대리점 선택", + "stuff.search.schReceiveUser": "담당자", + "stuff.search.period": "기간검색", + "stuff.search.schDateTypeU": "갱신일", + "stuff.search.schDateTypeR": "등록일", + "stuff.search.grid.title": "물건목록", + "stuff.search.grid.all": "전체", + "stuff.search.grid.selected": "선택", + "stuff.search.grid.schSortTypeR": "최근 등록일", + "stuff.search.grid.schSortTypeU": "최근 갱신일", + "stuff.windSelectPopup.title": "풍속선택", + "stuff.windSelectPopup.table.selected": "선택", + "stuff.windSelectPopup.table.windspeed": "풍속", + "stuff.windSelectPopup.error.message1": "주소를 먼저 검색해주세요.", + "stuff.windSelectPopup.error.message2": "풍속을 선택해주세요.", + "stuff.windSelectPopup.search.address1": "현", + "stuff.windSelectPopup.btn1": "닫기", + "stuff.windSelectPopup.btn2": "선택", "length": "길이", "height": "높이", "output": "출력", @@ -429,7 +588,7 @@ "main.popup.login.validate1": "입력한 패스워드가 다릅니다.", "main.popup.login.validate2": "반각 10자 이내로 입력해주세요.", "main.popup.login.success": "비밀번호가 변경되었습니다.", - "surface.shape.validate.size": "사이즈를 입력해 주세요.", + "common.canvas.validate.size": "사이즈를 입력해 주세요.", "surface.shape.validate.size.1to2": "①길이는 ②보다 큰 값을 넣어주세요.", "surface.shape.validate.size.1to3": "①길이는 ③보다 큰 값을 넣어주세요.", "surface.shape.validate.size.1to23": "①길이는 ②+③보다 큰 값을 넣어주세요.", diff --git a/src/store/boardAtom.js b/src/store/boardAtom.js new file mode 100644 index 00000000..3444eba0 --- /dev/null +++ b/src/store/boardAtom.js @@ -0,0 +1,14 @@ +import { atom } from 'recoil' + +export const searchState = atom({ + key: 'searchState', + default: { + currentPage: 1, + totalPage: 1, + pageBlock: 100, + totalCount: 0, + searchValue: '', + mainFlag: 'N', + searchFlag: false, + }, +}) diff --git a/src/store/canvasAtom.js b/src/store/canvasAtom.js index c622283b..7806b8f2 100644 --- a/src/store/canvasAtom.js +++ b/src/store/canvasAtom.js @@ -35,7 +35,7 @@ export const fontFamilyState = atom({ export const fontSizeState = atom({ key: 'fontSizeState', - default: 16, + default: 20, }) export const canvasSizeState = atom({ diff --git a/src/store/placementShapeDrawingAtom.js b/src/store/placementShapeDrawingAtom.js new file mode 100644 index 00000000..b7a65107 --- /dev/null +++ b/src/store/placementShapeDrawingAtom.js @@ -0,0 +1,70 @@ +import { atom } from 'recoil' + +export const OUTER_LINE_TYPE = { + OUTER_LINE: 'outerLine', // 외벽선 + RIGHT_ANGLE: 'rightAngle', // 직각 + DOUBLE_PITCH: 'doublePitch', + ANGLE: 'angle', // 각도 + DIAGONAL_LINE: 'diagonalLine', // 대각선 +} + +/** + * 외벽선 작성에서 사용하는 recoilState + */ + +export const placementShapeDrawingLength1State = atom({ + //길이1 + key: 'placementShapeDrawingLength1State', + default: 0, +}) + +export const placementShapeDrawingLength2State = atom({ + // 길이2 + key: 'placementShapeDrawingLength2State', + default: 0, +}) + +export const placementShapeDrawingArrow1State = atom({ + // 방향1 + key: 'placementShapeDrawingArrow1State', + default: '', +}) + +export const placementShapeDrawingArrow2State = atom({ + // 방향2 + key: 'placementShapeDrawingArrow2State', + default: '', +}) + +export const placementShapeDrawingAngle1State = atom({ + // 각도1 + key: 'placementShapeDrawingAngle1State', + default: 0, +}) + +export const placementShapeDrawingAngle2State = atom({ + // 각도2 + key: 'placementShapeDrawingAngle2State', + default: 0, +}) + +export const placementShapeDrawingDiagonalState = atom({ + // 대각선 + key: 'placementShapeDrawingDiagonalState', + default: 0, +}) + +export const placementShapeDrawingTypeState = atom({ + key: 'placementShapeDrawingTypeState', + default: OUTER_LINE_TYPE.OUTER_LINE, +}) + +export const placementShapeDrawingPointsState = atom({ + key: 'placementShapeDrawingPointsState', + default: [], +}) + +export const placementShapeDrawingFixState = atom({ + key: 'placementShapeDrawingFixState', + default: false, +}) diff --git a/src/store/planReqAtom.js b/src/store/planReqAtom.js new file mode 100644 index 00000000..fddf1374 --- /dev/null +++ b/src/store/planReqAtom.js @@ -0,0 +1,22 @@ +import { atom } from 'recoil' +import dayjs from 'dayjs' +import { v1 } from 'uuid' +export const planReqSearchState = atom({ + key: `planReqSearchState/${v1()}`, + default: { + saleStoreId: '', //판매점ID 세션 + saleStoreLevel: '', //판매점레벨 세션 + schPlanReqNo: '', //설계의뢰 번호 + schTitle: '', //안건명 + schAddress: '', //도도부현 + schSaleStoreName: '', //판매대리점명 + schPlanReqName: '', //의뢰자명 + schPlanStatCd: '', //상태코드 + schDateGbn: 'S', //기간구분코드(S/R) + schStartDt: dayjs(new Date()).add(-3, 'month').format('YYYY-MM-DD'), //시작일 + schEndDt: dayjs(new Date()).format('YYYY-MM-DD'), //종료일 + startRow: 1, + endRow: 20, + }, + dangerouslyAllowMutability: true, +}) diff --git a/src/store/stuffAtom.js b/src/store/stuffAtom.js index c47321ae..ceb5def0 100644 --- a/src/store/stuffAtom.js +++ b/src/store/stuffAtom.js @@ -18,6 +18,10 @@ export const stuffSearchState = atom({ startRow: 1, endRow: 100, schSortType: 'R', //정렬조건 (R:최근등록일 U:최근수정일) + selObject: { + value: '', + label: '', + }, }, dangerouslyAllowMutability: true, }) diff --git a/src/styles/_contents.scss b/src/styles/_contents.scss index dc02fc85..37233d13 100644 --- a/src/styles/_contents.scss +++ b/src/styles/_contents.scss @@ -627,9 +627,9 @@ justify-content: flex-end; margin-top: 20px; } - .pagination-wrap{ - margin-top: 24px; - } +} +.pagination-wrap{ + margin-top: 24px; } .infomation-wrap{ @@ -811,7 +811,10 @@ font-size: 12px; font-weight: 400; color: #45576F; - margin-bottom: 5px; + margin-bottom: 8px; + &:last-child{ + margin-bottom: 0; + } } } &::-webkit-scrollbar { @@ -1090,4 +1093,111 @@ } } } +} + +.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 .15s ease-in-out; + cursor: pointer; + .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; + } + } } \ No newline at end of file diff --git a/src/styles/_grid-detail.scss b/src/styles/_grid-detail.scss index 4cf9d3b2..048a1946 100644 --- a/src/styles/_grid-detail.scss +++ b/src/styles/_grid-detail.scss @@ -1,5 +1,5 @@ .q-grid{ - height: fit-content; + position: relative; .ag-theme-quartz { outline: none; border: none; @@ -61,4 +61,68 @@ margin-left: 12px; } } + &.no-cols{ + .ag-row{ + &:nth-child(2n){ + background-color: #fff; + } + } + } + .form-flex-wrap{ + display: flex; + align-items: center; + width: 100%; + .grid-tip{ + margin-left: auto; + } + } +} + +// grid-button +.grid-cell-btn{ + display: flex; + flex-direction: column; + gap: 5px; + width: 117px; + margin: 0 auto; + .grid-btn{ + display: block; + width: 100%; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + background-color: #fff; + border: 1px solid #94A0AD; + background-color: transparent; + border-radius: 2px; + font-size: 13px; + color: #94A0AD; + font-weight: 400; + text-align: center; + span{ + display: block; + margin-right: 5px; + &.file{ + width: 13px; + height: 14px; + background: url(../../public/static/images/sub/grid-btn-file.svg)no-repeat center; + background-size: cover; + } + &.excel{ + width: 14px; + height: 13px; + background: url(../../public/static/images/sub/grid-btn-excel.svg)no-repeat center; + background-size: cover; + } + } + } +} + +.grid-tip{ + display: block; + width: 15px; + height: 15px; + background: url(../../public/static/images/sub/grid_tip.svg)no-repeat center; + background-size: cover; } \ No newline at end of file diff --git a/src/styles/_modal.scss b/src/styles/_modal.scss index d70732b8..b82d434c 100644 --- a/src/styles/_modal.scss +++ b/src/styles/_modal.scss @@ -14,7 +14,6 @@ $alert-color: #101010; scale: 1; } } - @keyframes unmountpop { from { opacity: 1; @@ -31,7 +30,6 @@ $alert-color: #101010; font-weight: 400; color: #fff; } - .bold-font { font-size: 12px; font-weight: 500; @@ -48,67 +46,54 @@ $alert-color: #101010; 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; } - &.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; + animation: mountpop 0.17s ease-in-out forwards; } - &.unmount { - animation: unmountpop .17s ease-in-out forwards; + animation: unmountpop 0.17s ease-in-out forwards; } - &.alert { position: absolute; top: 50%; @@ -116,25 +101,21 @@ $alert-color: #101010; 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; @@ -144,7 +125,6 @@ $alert-color: #101010; } } } - .modal-head { display: flex; align-items: center; @@ -156,7 +136,6 @@ $alert-color: #101010; color: $pop-color; font-weight: 700; } - .modal-close { margin-left: auto; color: transparent; @@ -166,64 +145,50 @@ $alert-color: #101010; 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; @@ -231,22 +196,18 @@ $alert-color: #101010; } } } - .outer-line-wrap { - border-top: 1px solid #3C3C3C; + 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; @@ -258,17 +219,15 @@ $alert-color: #101010; .adsorption-point { display: flex; align-items: center; - background-color: #3A3A3A; + 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; @@ -278,11 +237,10 @@ $alert-color: #101010; font-size: 13px; color: #898989; } - &.act { i { color: $pop-color; - background-color: #1083E3; + background-color: #1083e3; } } } @@ -293,83 +251,68 @@ $alert-color: #101010; 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: 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; } } } - .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; @@ -377,11 +320,9 @@ $alert-color: #101010; } } } - .grid-btn-wrap { padding-top: 15px; text-align: right; - button { padding: 0 10px; } @@ -393,16 +334,13 @@ $alert-color: #101010; color: $pop-color; font-weight: $pop-normal-weight; padding-bottom: 15px; - } - .grid-direction { display: flex; align-items: center; gap: 5px; flex: 1; } - .direction { width: 22px; height: 22px; @@ -412,21 +350,17 @@ $alert-color: #101010; background-position: center; background-size: 16px 15px; border-radius: 50%; - transition: all .15s ease-in-out; + 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; @@ -441,26 +375,21 @@ $alert-color: #101010; 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%; } } } - .direction-move-wrap { flex: none; display: grid; @@ -472,7 +401,6 @@ $alert-color: #101010; .placement-table { table { table-layout: fixed; - tr { th { display: flex; @@ -483,14 +411,12 @@ $alert-color: #101010; padding: 18px 0; border-bottom: 1px solid #424242; } - td { font-size: $pop-normal-size; color: $pop-color; border-bottom: 1px solid #424242; padding-left: 20px; } - &:first-child { td, th { @@ -499,7 +425,6 @@ $alert-color: #101010; } } } - .tooltip { position: relative; display: block; @@ -509,20 +434,17 @@ $alert-color: #101010; background: url(../../public/static/images/canvas/pop_tip.svg) no-repeat center; background-size: cover; } - &.light { padding: 0; - - th, td { + th, + td { color: $alert-color; border-bottom: none; - border-top: 1px solid #EFEFEF; + border-top: 1px solid #efefef; } - th { padding: 14px 0; } - tr { &:first-child { td, @@ -530,7 +452,6 @@ $alert-color: #101010; padding-top: 14px; } } - &:last-child { td, th { @@ -546,24 +467,20 @@ $alert-color: #101010; align-items: center; gap: 10px; } - .placement-option { display: flex; align-items: center; gap: 20px; } - .select-wrap { - div { + .sort-select { width: 100%; } } - .flex-ment { display: flex; align-items: center; gap: 5px; - span { font-size: $pop-normal-size; color: $pop-color; @@ -580,22 +497,18 @@ $alert-color: #101010; 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 { display: flex; align-items: center; @@ -607,7 +520,6 @@ $alert-color: #101010; font-weight: $pop-bold-weight; color: $pop-color; margin-right: 10px; - &.thin { width: auto; font-weight: $pop-normal-weight; @@ -628,7 +540,6 @@ $alert-color: #101010; background-size: 12px 12px; background-position: center; } - &:last-child { margin-right: 0; } @@ -636,28 +547,24 @@ $alert-color: #101010; .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; align-items: center; justify-content: center; width: 50%; - background-color: #3D3D3D; + background-color: #3d3d3d; border-radius: 2px; } } @@ -665,7 +572,7 @@ $alert-color: #101010; // 외벽선 속성 설정 .properties-guide { font-size: $pop-normal-size; - color: #AAA; + color: #aaa; font-weight: $pop-normal-weight; margin-bottom: 14px; } @@ -676,19 +583,16 @@ $alert-color: #101010; 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%; @@ -697,21 +601,17 @@ $alert-color: #101010; color: #fff; font-weight: 700; border-radius: 2px; - transition: all .15s ease-in-out; - + transition: all 0.15s ease-in-out; &.green { background-color: #305941; - border: 1px solid #45CD7D; - + border: 1px solid #45cd7d; &:hover { background-color: #3a6b4e; } } - &.blue { - background-color: #2E5360; - border: 1px solid #3FBAE6; - + background-color: #2e5360; + border: 1px solid #3fbae6; &:hover { background-color: #365f6e; } @@ -727,39 +627,34 @@ $alert-color: #101010; 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; - + 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 .15s ease-in-out; + transition: color 0.15s ease-in-out; } - .shape-menu-box { &.act, &:hover { .shape-box { - background-color: #008BFF; + background-color: #008bff; } - .shape-title { - color: #008BFF; + color: #008bff; } } } @@ -770,14 +665,12 @@ $alert-color: #101010; 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: 1px solid #3d3d3d; border-radius: 2px; } @@ -791,10 +684,8 @@ $alert-color: #101010; .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; @@ -804,26 +695,22 @@ $alert-color: #101010; 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; + background-color: #3d3d3d; + border: 1px solid #3d3d3d; border-radius: 2px; cursor: pointer; - &.act { - border: 1px solid #ED0004; + border: 1px solid #ed0004; } } - &:last-child { .eaves-keraba-th, .eaves-keraba-td { @@ -832,17 +719,14 @@ $alert-color: #101010; } } } - .guide { font-size: $pop-normal-size; font-weight: $pop-normal-weight; color: $pop-color; margin-bottom: 24px; - &.sm { margin-bottom: 15px; } - span { display: block; } @@ -855,14 +739,12 @@ $alert-color: #101010; 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; @@ -874,7 +756,6 @@ $alert-color: #101010; font-weight: $pop-normal-weight; border: 1px solid #484848; background-color: #323234; - i { display: block; width: 12px; @@ -891,14 +772,11 @@ $alert-color: #101010; align-items: center; gap: 10px; margin-bottom: 10px; - .flex-ment { gap: 10px; - .dec { text-decoration: underline; } - .delete { display: block; width: 15px; @@ -907,7 +785,6 @@ $alert-color: #101010; background-size: cover; } } - &:last-child { margin-bottom: 0; } @@ -918,7 +795,6 @@ $alert-color: #101010; display: flex; align-items: center; gap: 5px; - button { display: flex; align-items: center; @@ -931,8 +807,7 @@ $alert-color: #101010; border: 1px solid #646464; border-radius: 2px; padding: 0 10px; - transition: all .15s ease-in-out; - + transition: all 0.15s ease-in-out; i { height: 15px; display: block; @@ -940,30 +815,25 @@ $alert-color: #101010; background-repeat: no-repeat; background-position: center; background-size: cover; - transition: all .15s ease-in-out; - + 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); } @@ -985,13 +855,11 @@ $alert-color: #101010; grid-template-rows: repeat(3, 90px); gap: 10px; margin-bottom: 10px; - .shape-menu-box { border-radius: 2px; - background-color: #3D3D3D; + background-color: #3d3d3d; padding: 8px; - transition: all .15s ease-in-out; - + transition: all 0.15s ease-in-out; .shape-box { display: flex; justify-content: center; @@ -1002,10 +870,9 @@ $alert-color: #101010; background-color: #313131; border-radius: 2px; } - &.act, &:hover { - background-color: #008BFF; + background-color: #008bff; } } } @@ -1016,37 +883,32 @@ $alert-color: #101010; justify-content: center; gap: 5px; padding: 5px; - background-color: #3D3D3D; + background-color: #3d3d3d; margin-bottom: 24px; - .library-btn { width: 30px; height: 30px; - border: 1px solid #6C6C6C; + border: 1px solid #6c6c6c; border-radius: 2px; background-color: transparent; background-repeat: no-repeat; background-position: center; - transition: all .15s ease-in-out; - + transition: all 0.15s ease-in-out; &.ico01 { background-image: url(../../public/static/images/canvas/shape_labrary01.svg); background-size: 14px 14px; } - &.ico02 { background-image: url(../../public/static/images/canvas/shape_labrary02.svg); background-size: 13px 17px; } - &.ico03 { background-image: url(../../public/static/images/canvas/shape_labrary03.svg); background-size: 17px 13px; } - &:hover { - border-color: #1083E3; - background-color: #1083E3; + border-color: #1083e3; + background-color: #1083e3; } } } @@ -1054,33 +916,27 @@ $alert-color: #101010; .plane-shape-wrapper { display: flex; gap: 10px; - .plane-box { padding: 10px; border-radius: 2px; - background-color: #3D3D3D; - + background-color: #3d3d3d; .plane-box-tit { font-size: $pop-normal-size; font-weight: 600; color: $pop-color; margin-bottom: 10px; } - &.shape-box { flex: 1; - .shape-box-inner { display: flex; gap: 10px; min-height: 140px; - .shape-img { position: relative; flex: 1; background-color: #fff; border-radius: 2px; - img { position: absolute; top: 50%; @@ -1088,21 +944,18 @@ $alert-color: #101010; transform: translate(-50%, -50%); } } - .shape-data { flex: none; width: 190px; background-color: #313131; border-radius: 2px; padding: 15px; - .eaves-keraba-table { .eaves-keraba-item { .eaves-keraba-th, .eaves-keraba-td { padding-bottom: 10px; } - &:last-child { .eaves-keraba-th, .eaves-keraba-td { @@ -1114,13 +967,11 @@ $alert-color: #101010; } } } - &.direction-box { display: flex; flex-direction: column; flex: none; width: 180px; - .plane-direction-box { flex: 1; display: flex; @@ -1132,43 +983,36 @@ $alert-color: #101010; } } } - .plane-direction { width: 150px; position: relative; height: 120px; - span { position: absolute; font-size: 12px; font-weight: 500; - color: #B1B1B1; - + 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; @@ -1179,32 +1023,27 @@ $alert-color: #101010; background-repeat: no-repeat; background-position: center; border-radius: 50%; - transition: all .15s ease-in-out; - + 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; @@ -1232,7 +1071,6 @@ $alert-color: #101010; align-items: center; justify-content: center; } - .discrimination-tit { font-size: 13px; color: #fff; @@ -1244,13 +1082,11 @@ $alert-color: #101010; 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%; @@ -1264,11 +1100,10 @@ $alert-color: #101010; .display-change-wrap { margin: 24px 0; } - .warning { font-size: $pop-normal-size; font-weight: $pop-normal-weight; - color: #FFAFAF; + color: #ffafaf; } // 각 변 속성 변경 @@ -1282,12 +1117,10 @@ $alert-color: #101010; .drawing-flow-wrap { display: flex; gap: 10px; - .discrimination-box { flex: 1; display: flex; flex-direction: column; - .object-direction-wrap { flex: 1; } @@ -1299,7 +1132,6 @@ $alert-color: #101010; align-items: center; justify-content: center; } - .compas-box-inner { position: relative; width: 200px; @@ -1323,206 +1155,157 @@ $alert-color: #101010; top: 12.5px; left: 50%; font-size: 11px; - color: #8B8B8B; + 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: ''; @@ -1534,13 +1317,11 @@ $alert-color: #101010; height: 5px; background-color: #fff; } - i { color: #fff; } } } - .compas { position: absolute; top: 50%; @@ -1550,7 +1331,6 @@ $alert-color: #101010; height: 148px; border: 4px solid #fff; border-radius: 50%; - .compas-arr { width: 100%; height: 100%; @@ -1560,14 +1340,12 @@ $alert-color: #101010; } } - // 지붕모듈선택 .roof-module-tab { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; - .module-tab-bx { flex: 1; height: 34px; @@ -1576,19 +1354,17 @@ $alert-color: #101010; border-radius: 2px; background-color: transparent; font-size: 12px; - color: #AAA; + color: #aaa; text-align: center; cursor: default; - transition: all .15s ease-in-out; - + transition: all 0.15s ease-in-out; &.act { - background-color: #1083E3; - border: 1px solid #1083E3; + background-color: #1083e3; + border: 1px solid #1083e3; color: #fff; font-weight: 500; } } - .tab-arr { display: block; width: 9px; @@ -1597,8 +1373,7 @@ $alert-color: #101010; background-position: center; background-size: cover; background-image: url(../../public/static/images/canvas/module_tab_arr.svg); - transition: all .15s ease-in-out; - + transition: all 0.15s ease-in-out; &.act { background-image: url(../../public/static/images/canvas/module_tab_arr_white.svg); } @@ -1607,14 +1382,11 @@ $alert-color: #101010; .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), @@ -1627,23 +1399,20 @@ $alert-color: #101010; transform: translateX(-50%); width: 1px; height: 6px; - background-color: #8B8B8B; + background-color: #8b8b8b; } } - i { top: 32px; } - &.act { i { - color: #8B8B8B; + color: #8b8b8b; } } } } } - .center-wrap { display: flex; flex-direction: column; @@ -1654,7 +1423,6 @@ $alert-color: #101010; .module-table-flex-wrap { display: flex; gap: 10px; - .outline-form { flex: 1; } @@ -1662,7 +1430,6 @@ $alert-color: #101010; .module-box-tab { display: flex; - .module-btn { flex: 1; border-top: 1px solid #505050; @@ -1673,12 +1440,10 @@ $alert-color: #101010; height: 30px; font-size: 12px; font-weight: 400; - transition: all .15s ease-in-out; - + transition: all 0.15s ease-in-out; &:first-child { border-left: 1px solid #505050; } - &.act { border-color: #fff; background-color: #fff; @@ -1689,39 +1454,32 @@ $alert-color: #101010; .module-table-box { flex: 1; - background-color: #3D3D3D; + 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; + 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; @@ -1729,25 +1487,20 @@ $alert-color: #101010; padding-bottom: 15px; } } - .warning-guide { padding: 20px; - .warning { - color: #FFCACA; + color: #ffcaca; max-height: 55px; overflow-y: auto; padding-right: 30px; - &::-webkit-scrollbar { width: 4px; background-color: transparent; } - &::-webkit-scrollbar-thumb { - background-color: #D9D9D9; + background-color: #d9d9d9; } - &::-webkit-scrollbar-track { background-color: transparent; } @@ -1757,27 +1510,23 @@ $alert-color: #101010; .module-self-table { display: table; - border-top: 1px solid #4D4D4D; + 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; + 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; @@ -1791,35 +1540,28 @@ $alert-color: #101010; display: flex; align-items: center; margin-top: 15px; - button { margin-left: auto; } } - .hexagonal-wrap { .hexagonal-item { padding: 15px 0; - border-bottom: 1px solid #4D4D4D; - + 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; } @@ -1835,20 +1577,16 @@ $alert-color: #101010; .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; + background-color: #d9d9d9; } - &::-webkit-scrollbar-track { background-color: transparent; } @@ -1865,9 +1603,20 @@ $alert-color: #101010; gap: 5px; min-height: 60px; padding: 12px; - border: 1px solid rgba(255, 255, 255, 0.20); + 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; @@ -1879,19 +1628,16 @@ $alert-color: #101010; } .circuit-overflow { - max-height: 560px; + max-height: 400px; overflow-y: auto; margin-bottom: 15px; - &::-webkit-scrollbar { width: 4px; background-color: transparent; } - &::-webkit-scrollbar-thumb { - background-color: #D9D9D9; + background-color: #d9d9d9; } - &::-webkit-scrollbar-track { background-color: transparent; } @@ -1901,16 +1647,13 @@ $alert-color: #101010; 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; @@ -1932,7 +1675,6 @@ $alert-color: #101010; gap: 15px 0; margin-bottom: 24px; } - .additional-wrap { padding: 24px 0; border-top: 1px solid #424242; @@ -1943,26 +1685,293 @@ $alert-color: #101010; 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 #EA10AC; - background-color: #16417D; + border: 2px solid #ce1c9c; + background-color: #16417d; } - &.white { - border: 2px solid #FFF; + border: 2px solid #fff; background-color: #001027; } } } -} \ No newline at end of file +} + +// 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%; + 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 0 20px; + .line-form { + position: relative; + width: 102px; + height: 40px; + margin: 0 auto; + border-left: 1px dashed #101010; + border-right: 1px dashed #101010; + .line-font-box { + position: absolute; + bottom: -3px; + left: 0; + width: 100%; + text-align: center; + .font { + display: block; + padding-bottom: 6px; + color: #101010; + } + .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; + } + } +} diff --git a/src/styles/_reset.scss b/src/styles/_reset.scss index b575113b..47cb3efa 100644 --- a/src/styles/_reset.scss +++ b/src/styles/_reset.scss @@ -1,792 +1,1025 @@ * { - -webkit-text-size-adjust:none; - -moz-text-size-adjust:none; - -ms-text-size-adjust:none; - text-size-adjust: none; - box-sizing: content-box + -webkit-text-size-adjust: none; + -moz-text-size-adjust: none; + -ms-text-size-adjust: none; + text-size-adjust: none; + box-sizing: content-box; } -*, ::after, ::before { - box-sizing: border-box; +*, +::after, +::before { + box-sizing: border-box; } -html, body{ - width: 100%; - height: 100%; - font-size: 16px; +html, +body { + width: 100%; + height: 100%; + font-size: 16px; } -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font: inherit; - vertical-align: baseline; - font-family: 'Noto Sans JP', sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - font-smooth: never; +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font: inherit; + vertical-align: baseline; + font-family: 'Noto Sans JP', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-smooth: never; } /* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; } body { - line-height: 1.4; + line-height: 1.4; +} +body:first-of-type caption { + display: none; } -body:first-of-type caption { display:none;} -ol, ul { - list-style: none; +ol, +ul { + list-style: none; } -blockquote, q { - quotes: none; +blockquote, +q { + quotes: none; } -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; } table { - width: 100%; - border-collapse: separate; - border-spacing:0; - border:0 none; + width: 100%; + border-collapse: separate; + border-spacing: 0; + border: 0 none; } -caption, th, td { - text-align:left; - font-weight: normal; - border:0; +caption, +th, +td { + text-align: left; + font-weight: normal; + border: 0; } -a { - cursor:pointer; - color:#000; +a { + cursor: pointer; + color: #000; } -a, a:hover, a:active { - text-decoration:none; - -webkit-tap-highlight-color: transparent; +a, +a:hover, +a:active { + text-decoration: none; + -webkit-tap-highlight-color: transparent; } /*form_style*/ -input, select, textarea, button, a, label { - -webkit-tap-highlight-color:rgba(0,0,0,0); +input, +select, +textarea, +button, +a, +label { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } -button,input[type=text], input[type=button] { - -webkit-appearance: none; - -webkit-border-radius: 0; - -webkit-appearance:none; - appearance: none; - border-radius: 0 +button, +input[type='text'], +input[type='button'] { + -webkit-appearance: none; + -webkit-border-radius: 0; + -webkit-appearance: none; + appearance: none; + border-radius: 0; } -input[type=checkbox], input[type=radio] { - box-sizing: border-box; - padding: 0; +input[type='checkbox'], +input[type='radio'] { + box-sizing: border-box; + padding: 0; } -input, select, button { - border:0 none; - outline:none; - margin:0; +input, +select, +button { + border: 0 none; + outline: none; + margin: 0; } select { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; } select::-ms-expand { - display: none; + display: none; } ::-webkit-input-placeholder { - line-height:1; - font-weight:300; - font-size:0.938rem; - letter-spacing:-0.6px; - color:#8b8b8b; + line-height: 1; + font-weight: 300; + font-size: 0.938rem; + letter-spacing: -0.6px; + color: #8b8b8b; } -.log-box ::-webkit-input-placeholder{ - color:#8b8b8b; +.log-box ::-webkit-input-placeholder { + color: #8b8b8b; } -button{ - background: transparent; - font-family: 'Noto Sans JP', sans-serif; - border: none; - padding: 0; - margin: 0; - line-height: 1.4; - color: inherit; - outline: none; - cursor: pointer; +button { + background: transparent; + font-family: 'Noto Sans JP', sans-serif; + border: none; + padding: 0; + margin: 0; + line-height: 1.4; + color: inherit; + outline: none; + cursor: pointer; } -.pre{ - font-family: 'Pretendard', sans-serif !important; +.pre { + font-family: 'Pretendard', sans-serif !important; } // margin -.mt5{margin-top: 5px !important;} -.mt10{margin-top: 10px !important;} -.mt15{margin-top: 15px !important;} -.mb5{margin-bottom: 5px !important;} -.mb10{margin-bottom: 10px !important;} -.mb15{margin-bottom: 15px !important;} -.mr5{margin-right: 5px !important;} -.mr10{margin-right: 10px !important;} -.mr15{margin-right: 15px !important;} -.ml5{margin-left: 5px !important;} -.ml10{margin-left: 10px !important;} -.ml15{margin-left: 15px !important;} - -// align -.al-l{text-align: left !important;} -.al-r{text-align: right !important;} -.al-c{text-align: center !important;} - - -// button -.btn-frame{ - display: inline-block; - padding: 0 7px; - height: 34px; - line-height: 34px; - border-radius: 2px; - color: #fff; - font-size: 12px; - font-weight: 400; - border: 1px solid #000; - text-align: center; - font-family: 'Pretendard', sans-serif; - transition: all .17s ease-in-out; - cursor: pointer; - &.block{ - width: 100%; - } - &.small{ - font-family: 'Noto Sans JP', sans-serif; - height: 30px; - line-height: 30px; - font-size: 13px; - } - - &.deepgray{ - background-color: #2C2C2C; - border: 1px solid #484848; - } - &.gray{ - background-color: #3C3C3C; - border: 1px solid #545454; - } - &.dark{ - background-color: #1C1C1C; - border: 1px solid #484848; - } - &.modal{ - font-family: 'Noto Sans JP', sans-serif; - background-color: #272727; - border: 1px solid #484848; - color: #aaa; - &:hover{ - background-color: #1083E3; - border: 1px solid #1083E3; - color: #fff; - font-weight: 500; - } - } - &.sub-tab{ - height: 30px; - padding: 0 10px; - line-height: 28px; - font-family: 'Noto Sans JP', sans-serif; - background-color: #2D2D2D; - border: 1px solid #393939; - color: #aaa; - &.act, - &:hover{ - background-color: #414E6C; - border: 1px solid #414E6C; - color: #fff; - font-weight: 500; - } - } - &.roof{ - height: 30px; - padding: 0 10px; - line-height: 28px; - font-family: 'Noto Sans JP', sans-serif; - background-color: transparent; - border: 1px solid #484848; - color: #fff; - &.blue{ - background-color: #414E6C; - border: 1px solid #414E6C; - &:hover{ - background-color: #414E6C; - border: 1px solid #414E6C; - } - } - &.white{ - background-color: #fff; - border: 1px solid #fff; - color: #101010; - &:hover{ - background-color: #fff; - border: 1px solid #fff; - color: #101010; - } - } - &:hover{ - font-weight: 400; - background-color: transparent; - border: 1px solid #484848; - color: #fff; - } - } - &.self{ - height: 30px; - padding: 0 10px; - line-height: 28px; - font-family: 'Noto Sans JP', sans-serif; - background-color: transparent; - border: 1px solid #676767; - color: #AAAAAA; - &:hover{ - background-color: #272727; - border-color: #676767; - font-weight: 400; - } - } - &:hover, - &.act{ - background-color: #1083E3; - border: 1px solid #1083E3; - color: #fff; - font-weight: 500; - } - &.block{ - display: block; - width: 100%; - } - &.ico-flx{ - display: flex; - align-items: center; - .ico{ - margin-right: 10px; - } - &:hover, - &.act{ - font-weight: 400; - } - } +.mt5 { + margin-top: 5px !important; +} +.mt10 { + margin-top: 10px !important; +} +.mt15 { + margin-top: 15px !important; +} +.mb5 { + margin-bottom: 5px !important; +} +.mb10 { + margin-bottom: 10px !important; +} +.mb15 { + margin-bottom: 15px !important; +} +.mr5 { + margin-right: 5px !important; +} +.mr10 { + margin-right: 10px !important; +} +.mr15 { + margin-right: 15px !important; +} +.ml5 { + margin-left: 5px !important; +} +.ml10 { + margin-left: 10px !important; +} +.ml15 { + margin-left: 15px !important; } -.btn-origin{ - display: inline-block; +// align +.al-l { + text-align: left !important; +} +.al-r { + text-align: right !important; +} +.al-c { + text-align: center !important; +} + +// button +.btn-frame { + display: inline-block; + padding: 0 7px; + height: 34px; + line-height: 34px; + border-radius: 2px; + color: #fff; + font-size: 12px; + font-weight: 400; + border: 1px solid #000; + text-align: center; + font-family: 'Pretendard', sans-serif; + transition: all 0.17s ease-in-out; + cursor: pointer; + &.block { + width: 100%; + } + &.small { + font-family: 'Noto Sans JP', sans-serif; + height: 30px; + line-height: 30px; + font-size: 13px; + } + + &.deepgray { + background-color: #2c2c2c; + border: 1px solid #484848; + } + &.gray { + background-color: #3c3c3c; + border: 1px solid #545454; + } + &.dark { + background-color: #1c1c1c; + border: 1px solid #484848; + } + &.modal { + font-family: 'Noto Sans JP', sans-serif; + background-color: #272727; + border: 1px solid #484848; + color: #aaa; + &:hover { + background-color: #1083e3; + border: 1px solid #1083e3; + color: #fff; + font-weight: 500; + } + } + &.sub-tab { height: 30px; padding: 0 10px; - border-radius: 2px; - background-color: #101010; + line-height: 28px; + font-family: 'Noto Sans JP', sans-serif; + background-color: #2d2d2d; + border: 1px solid #393939; + color: #aaa; + &.act, + &:hover { + background-color: #414e6c; + border: 1px solid #414e6c; + color: #fff; + font-weight: 500; + } + } + &.roof { + height: 30px; + padding: 0 10px; + line-height: 28px; + font-family: 'Noto Sans JP', sans-serif; + background-color: transparent; + border: 1px solid #484848; color: #fff; - font-size: 13px; - font-weight: 400; - transition: all .15s ease-in-out; - &.navy{ - background-color: #304961; - &:hover{ - background-color: #233546; - } + &.blue { + background-color: #414e6c; + border: 1px solid #414e6c; + &:hover { + background-color: #414e6c; + border: 1px solid #414e6c; + } } - &.grey{ - background-color: #94A0AD; - &:hover{ - background-color: #607F9A; - } + &.white { + background-color: #fff; + border: 1px solid #fff; + color: #101010; + &:hover { + background-color: #fff; + border: 1px solid #fff; + color: #101010; + } } + &:hover { + font-weight: 400; + background-color: transparent; + border: 1px solid #484848; + color: #fff; + } + } + &.self { + height: 30px; + padding: 0 10px; + line-height: 28px; + font-family: 'Noto Sans JP', sans-serif; + background-color: transparent; + border: 1px solid #676767; + color: #aaaaaa; + &:hover { + background-color: #272727; + border-color: #676767; + font-weight: 400; + } + } + &:hover, + &.act { + background-color: #1083e3; + border: 1px solid #1083e3; + color: #fff; + font-weight: 500; + } + &.block { + display: block; + width: 100%; + } + &.ico-flx { + display: flex; + align-items: center; + .ico { + margin-right: 10px; + } + &:hover, + &.act { + font-weight: 400; + } + } +} + +.btn-origin { + display: inline-block; + height: 30px; + padding: 0 10px; + border-radius: 2px; + background-color: #101010; + color: #fff; + font-size: 13px; + font-weight: 400; + transition: all 0.15s ease-in-out; + &.navy { + background-color: #304961; + &:hover { + background-color: #233546; + } + } + &.grey { + background-color: #94a0ad; + &:hover { + background-color: #607f9a; + } + } + &.green { + background-color: #a6bba8; + &:hover { + background-color: #98af9b; + } + } + &.white { + border: 1px solid #94a0ad; + background-color: #fff; + color: #94a0ad; + &:hover { + background-color: #fff; + } + } } // select -.sort-select{ - position: relative; - display: inline-block; - min-width: 100px; - height: 30px; - line-height: 30px; - padding: 0 25px 0 10px; - background-color: #373737; - border: 1px solid #3F3F3F; - border-radius: 2px; - border-top-left-radius: 2px; +.sort-select { + position: relative; + display: inline-block; + min-width: 100px; + height: 30px; + line-height: 30px; + padding: 0 25px 0 10px; + background-color: #373737; + border: 1px solid #3f3f3f; + border-radius: 2px; + border-top-left-radius: 2px; + color: #fff; + cursor: pointer; + p { + font-size: 13px; color: #fff; - cursor: pointer; - p{ - font-size: 13px; + height: 100%; + } + .select-item-wrap { + position: absolute; + top: 100%; + left: -1px; + clip-path: inset(0 0 100% 0); + width: calc(100% + 2px); + padding: 8px 0; + max-height: 100px; + overflow-y: auto; + background-color: #373737; + border: 1px solid #3f3f3f; + border-radius: 2px; + transition: all 0.17s ease-in-out; + visibility: hidden; + z-index: 999; + .select-item { + display: flex; + align-items: center; + padding: 8px 20px; + line-height: 1.4; + transition: all 0.17s ease-in-out; + button { + font-size: 12px; color: #fff; - height: 100%; + line-height: 1.4; + } + &:hover { + background-color: #2c2c2c; + } } - .select-item-wrap{ - position: absolute; - top: 100%; - left: -1px; - clip-path:inset(0 0 100% 0); - width: calc(100% + 2px); - padding: 8px 0; - max-height: 100px; - overflow-y: auto; - background-color: #373737; - border: 1px solid #3F3F3F; - border-radius: 2px; - transition: all 0.17s ease-in-out; - visibility: hidden; - z-index: 999; - .select-item{ - display: flex; - align-items: center; - padding: 8px 20px; - line-height: 1.4; - transition: all .17s ease-in-out; - button{ - font-size: 12px; - color: #fff; - line-height: 1.4; - } - &:hover{ - background-color: #2C2C2C; - } - } - &::-webkit-scrollbar { - width: 2px; - background-color: transparent; - - } - &::-webkit-scrollbar-thumb { - background-color: #5a5a5a; - border-radius: 10px; - } - &::-webkit-scrollbar-track { - background-color: transparent; - } + &::-webkit-scrollbar { + width: 2px; + background-color: transparent; } - &::after{ - content: ''; - position: absolute; - top: 50%; - right: 7px; - transform: translateY(-50%); - width: 10px; - height: 6px; - background: url(/static/images/common/select-arr.svg) no-repeat center; - background-size: cover; - transition: all .17s ease-in-out; + &::-webkit-scrollbar-thumb { + background-color: #5a5a5a; + border-radius: 10px; } - &.active{ - .select-item-wrap{ - clip-path: inset(0 0 0 0); - visibility: visible; - } - &:after{ - transform: translateY(-50%) rotate(-180deg); - } + &::-webkit-scrollbar-track { + background-color: transparent; } + } + &::after { + content: ''; + position: absolute; + top: 50%; + right: 7px; + transform: translateY(-50%); + width: 10px; + height: 6px; + background: url(/static/images/common/select-arr.svg) no-repeat center; + background-size: cover; + transition: all 0.17s ease-in-out; + } + &.active { + .select-item-wrap { + clip-path: inset(0 0 0 0); + visibility: visible; + } + &:after { + transform: translateY(-50%) rotate(-180deg); + } + } } -.select-light{ - position: relative; +.select-light { + position: relative; + display: block; + width: 100%; + height: 30px; + background: #fff url(../../public/static/images/common/select_light_arr.svg) calc(100% - 11px) center no-repeat; + background-size: 10px 6px; + border: 1px solid #eee; + padding: 0 30px 0 10px; + font-size: 13px; + color: #45576f; + font-family: 'Noto Sans JP', sans-serif; + cursor: pointer; + &:disabled { + opacity: 1; + background-color: #fafafa; + color: #999; + cursor: default; + } + &.black { + color: #101010; + } + &.dark { + background: #323234 url(../../public/static/images/common/select_dark_arr.svg) calc(100% - 11px) center no-repeat; + color: #898989; + font-size: 12px; + border-radius: 2px; + border: none; + } +} + +// input +.form-input { + label { + display: block; + color: #aaa; + font-size: 12px; + font-weight: 500; + margin-bottom: 10px; + } +} +input[type='password'], +input[type='number'], +input[type='text'] { + &.input-origin { + display: inline-block; + height: 30px; + line-height: 30px; + border-radius: 2px; + background-color: #323234; + color: #fff; + font-size: 12px; + font-weight: 500; + font-family: 'Pretendard', sans-serif; + padding: 0 10px; + letter-spacing: 0px; + text-align: right; + &::placeholder { + opacity: 1; + font-size: 12px; + letter-spacing: 0px; + } + &.block { + width: 100%; + } + &:read-only { + color: #aaa; + } + &.plane { + font-family: 'Noto Sans JP', sans-serif; + border: 1px solid #525252; + background-color: transparent; + } + } + &.input-light { display: block; width: 100%; height: 30px; - background: #FFF url(../../public/static/images/common/select_light_arr.svg) calc(100% - 11px) center no-repeat; - background-size: 10px 6px; + padding: 0 10px; border: 1px solid #eee; - padding: 0 30px 0 10px; - font-size: 13px; - color: #45576F; + border-radius: 2px; + background-color: #fff; font-family: 'Noto Sans JP', sans-serif; - cursor: pointer; - &:disabled{ - opacity: 1; - background-color: #FAFAFA; - color: #999; - cursor: default; - } - &.black{ - color: #101010; - } - &.dark{ - background: #323234 url(../../public/static/images/common/select_dark_arr.svg) calc(100% - 11px) center no-repeat; - color: #898989; - font-size: 12px; - border-radius: 2px; - border: none; + font-size: 13px; + color: #45576f; + font-weight: normal; + transition: border-color 0.17s ease-in-out; + text-align: left; + &:read-only { + background-color: #fafafa; + color: #999999; } + } } - -// input -.form-input{ - label{ - display: block; - color: #aaa; - font-size: 12px; - font-weight: 500; - margin-bottom: 10px; - } -} -input[type=number], -input[type=text]{ - &.input-origin{ - display: inline-block; - height: 30px; - line-height: 30px; - border-radius: 2px; - background-color: #323234; - color: #fff; - font-size: 12px; - font-weight: 500; - font-family: 'Pretendard', sans-serif; - padding: 0 10px; - letter-spacing: 0px; - text-align: right; - &::placeholder{ - opacity: 1; - font-size: 12px; - letter-spacing: 0px; - } - &.block{ - width: 100%; - } - &:read-only{ - color: #AAA; - } - &.plane{ - font-family: 'Noto Sans JP', sans-serif; - border: 1px solid #525252; - background-color: transparent; - } - } - &.input-light{ - display: block; - width: 100%; - height: 30px; - padding: 0 10px; - border: 1px solid #eee; - border-radius: 2px; - background-color: #fff; - font-family: 'Noto Sans JP', sans-serif; - font-size: 13px; - color: #45576F; - font-weight: normal; - transition: border-color .17s ease-in-out; - text-align: left; - &:read-only{ - background-color: #FAFAFA; - color: #999999; - } - } -} - - - // check-btn -.check-btn{ - display: flex; - align-items: center; - height: 30px; - background-color: #3A3A3A; - border-radius: 3px; - transition: all .17s ease-in-out; - .check-area{ - flex: none; - width: 30px; - height: 100%; - border-right: 1px solid #272727; - background: url(../../public/static/images/canvas/check-grey.svg)no-repeat center; - background-size: 11px 9px; +.check-btn { + display: flex; + align-items: center; + height: 30px; + background-color: #3a3a3a; + border-radius: 3px; + transition: all 0.17s ease-in-out; + .check-area { + flex: none; + width: 30px; + height: 100%; + border-right: 1px solid #272727; + background: url(../../public/static/images/canvas/check-grey.svg) no-repeat center; + background-size: 11px 9px; + } + .title-area { + padding: 0 10px; + font-size: 12px; + color: #898989; + font-weight: 400; + } + &.block { + width: 100%; + } + &:hover, + &.act { + background-color: #fff; + .check-area { + border-right: 1px solid #101010; + background: url(../../public/static/images/canvas/check-black.svg) no-repeat center; } - .title-area{ - padding: 0 10px; - font-size: 12px; - color: #898989; - font-weight: 400; - } - &.block{ - width: 100%; - } - &:hover, - &.act{ - background-color: #fff; - .check-area{ - border-right: 1px solid #101010; - background: url(../../public/static/images/canvas/check-black.svg)no-repeat center; - } - .title-area{ - color: #101010; - font-weight: 600; - } + .title-area { + color: #101010; + font-weight: 600; } + } } // arr-btn -.arr-btn{ - display: block; - height: 30px; - border-radius: 3px; - background-color: #3A3A3A; - padding: 0 11px; - text-align: left; - transition: all .17s ease-in-out; - span{ - position: relative; - font-size: 12px; - color: #898989; - font-weight: 400; - padding-right: 15px; - &:after{ - content: ''; - position: absolute; - top: 50%; - right: 0; - transform: translateY(-50%); - width: 5px; - height: 8px; - background: url(../../public/static/images/canvas/arr_btn_ico.svg)no-repeat center; - } +.arr-btn { + display: block; + height: 30px; + border-radius: 3px; + background-color: #3a3a3a; + padding: 0 11px; + text-align: left; + transition: all 0.17s ease-in-out; + span { + position: relative; + font-size: 12px; + color: #898989; + font-weight: 400; + padding-right: 15px; + &:after { + content: ''; + position: absolute; + top: 50%; + right: 0; + transform: translateY(-50%); + width: 5px; + height: 8px; + background: url(../../public/static/images/canvas/arr_btn_ico.svg) no-repeat center; + } + } + &:hover, + &.act { + background-color: #fff; + span { + color: #101010; + font-weight: 500; + &:after { + background: url(../../public/static/images/canvas/arr_btn_ico_black.svg) no-repeat center; + } + } + } + &.dark { + text-align: center; + background-color: #272727; + border: 1px solid #484848; + span { + color: #fff; + &:after { + background: url(../../public/static/images/canvas/arr_btn_ico_white.svg) no-repeat center; + } } &:hover, - &.act{ - background-color: #fff; - span{ - color: #101010; - font-weight: 500; - &:after{ - background: url(../../public/static/images/canvas/arr_btn_ico_black.svg)no-repeat center; - } - } - } - &.dark{ - text-align: center; - background-color: #272727; - border: 1px solid #484848; - span{ - color: #Fff; - &:after{ - background: url(../../public/static/images/canvas/arr_btn_ico_white.svg)no-repeat center; - } - } - &:hover, - &.act{ - background-color: #1083E3; - border: 1px solid #1083E3; - } + &.act { + background-color: #1083e3; + border: 1px solid #1083e3; } + } } // radio .d-check-radio, -.d-check-box{ - line-height: 1.1; +.d-check-box { + line-height: 1.1; + cursor: pointer; + input[type='checkbox'], + input[type='radio'] { + position: static; + margin-left: 0; cursor: pointer; - input[type=checkbox], - input[type=radio]{ - position: static; - margin-left: 0; - cursor: pointer; - opacity: 0; - z-index: 1; - flex: 0 0 auto; + opacity: 0; + z-index: 1; + flex: 0 0 auto; + } + label { + position: relative; + padding-left: 10px; + margin-bottom: 0; + word-break: break-all; + line-height: 1.2; + display: inline; + vertical-align: top; + color: #fff; + font-size: 13px; + font-weight: 400; + cursor: pointer; + } + &.light { + label { + color: #45576f; } - label{ - position: relative; - padding-left: 10px; - margin-bottom: 0; - word-break: break-all; - line-height: 1.2; - display: inline; - vertical-align: top; - color: #fff; - font-size: 13px; - font-weight: 400; - cursor: pointer; - } - &.light{ - label{ - color: #45576F; - } - } - &.no-text{ - label{ - padding-left: 0; - } + } + &.no-text { + label { + padding-left: 0; } + } } .d-check-radio { - label{ - &::before{ - cursor: pointer; - content: ""; - display: inline-block; - position: absolute; - width: 17px; - height: 17px; - top:2px; - left: 0; - margin-left: -12px; - border: 1px solid #999999; - border-radius: 100%; - background-color: transparent; - text-align:center; - font-size:13px; - line-height:1.4; - transition: border 0.15s ease-in-out, color 0.15s ease-in-out; - } - &::after{ - cursor: pointer; - content: ""; - display: inline-block; - position: absolute; - width: 9px; - height: 9px; - top:6px; - left: 4px; - margin-left: -12px; - border: none; - border-radius: 100%; - background-color: #fff; - text-align:center; - font-size:13px; - line-height:1.4; - opacity: 0; - visibility: hidden; - transition: opacity 0.15s ease-in-out, color 0.15s ease-in-out; - } + label { + &::before { + cursor: pointer; + content: ''; + display: inline-block; + position: absolute; + width: 17px; + height: 17px; + top: 2px; + left: 0; + margin-left: -12px; + border: 1px solid #999999; + border-radius: 100%; + background-color: transparent; + text-align: center; + font-size: 13px; + line-height: 1.4; + transition: + border 0.15s ease-in-out, + color 0.15s ease-in-out; } - &.light{ - label{ - &:before{ - border-color: #D6D6D7; - } - &:after{ - background-color: #697C8F; - } - } + &::after { + cursor: pointer; + content: ''; + display: inline-block; + position: absolute; + width: 9px; + height: 9px; + top: 6px; + left: 4px; + margin-left: -12px; + border: none; + border-radius: 100%; + background-color: #fff; + text-align: center; + font-size: 13px; + line-height: 1.4; + opacity: 0; + visibility: hidden; + transition: + opacity 0.15s ease-in-out, + color 0.15s ease-in-out; } - input[type=radio]:checked + label::after{ - opacity: 1; - visibility: visible; + } + &.light { + label { + &:before { + border-color: #d6d6d7; + } + &:after { + background-color: #697c8f; + } } - &.pop{ - label{ - font-size: 12px; - &:before{ - width: 16px; - height: 16px; - border-color: #fff; - } - &:after{ - width: 8px; - height: 8px; - background-color: #fff; - } - } + } + input[type='radio']:checked + label::after { + opacity: 1; + visibility: visible; + } + &.pop { + label { + font-size: 12px; + &:before { + width: 16px; + height: 16px; + border-color: #fff; + } + &:after { + width: 8px; + height: 8px; + background-color: #fff; + } } + } } // check-box -.d-check-box{ - label{ - &.red{color: #FFCACA;} - &::before{ - cursor: pointer; - content: ""; - display: inline-block; - position: absolute; - width: 17px; - height: 17px; - top: 2px; - left: 0; - margin-left: -12px; - border: 1px solid #D6D6D7; - background-color: #fff; - transition: border 0.15s ease-in-out, color 0.15s ease-in-out; - } - &:after{ - cursor: pointer; - content: ""; - display: inline-block; - position: absolute; - width: 16px; - height: 16px; - top:0; - left: 0; - margin-left: -.8rem; - transition: border 0.05s ease-in-out, color 0.05s ease-in-out; - } +.d-check-box { + label { + &.red { + color: #ffcaca; } - input[type=checkbox]:checked + label::after{ - content: ""; - display: inline-block; - position: absolute; - top: 1px; - left: -1px; - width: 5px; - height: 8px; - border: 2px solid #697C8F; - border-left: none; - border-top: none; - transform: translate(7.75px,4.5px) rotate(45deg); - -ms-transform: translate(7.75px,4.5px) rotate(45deg); + &::before { + cursor: pointer; + content: ''; + display: inline-block; + position: absolute; + width: 17px; + height: 17px; + top: 2px; + left: 0; + margin-left: -12px; + border: 1px solid #d6d6d7; + background-color: #fff; + transition: + border 0.15s ease-in-out, + color 0.15s ease-in-out; } - &.pop{ - label{ - &:before{ - background-color: transparent; - } - } - input[type=checkbox]:checked + label::after{ - border-color: #fff; - } - &.no-text{ - label{ - padding-left: 0; - } - } + &:after { + cursor: pointer; + content: ''; + display: inline-block; + position: absolute; + width: 16px; + height: 16px; + top: 0; + left: 0; + margin-left: -0.8rem; + transition: + border 0.05s ease-in-out, + color 0.05s ease-in-out; } - &.sel{ - input[type=checkbox]:checked + label{ - color: #D7C863; - } - input[type=checkbox]:checked + label::before, - input[type=checkbox]:checked + label::after{ - border-color: #D7C863; - } + } + input[type='checkbox']:checked + label::after { + content: ''; + display: inline-block; + position: absolute; + top: 1px; + left: -1px; + width: 5px; + height: 8px; + border: 2px solid #697c8f; + border-left: none; + border-top: none; + transform: translate(7.75px, 4.5px) rotate(45deg); + -ms-transform: translate(7.75px, 4.5px) rotate(45deg); + } + &.pop { + label { + &:before { + background-color: transparent; + } } + input[type='checkbox']:checked + label::after { + border-color: #fff; + } + &.no-text { + label { + padding-left: 0; + } + } + } + &.sel { + input[type='checkbox']:checked + label { + color: #d7c863; + } + input[type='checkbox']:checked + label::before, + input[type='checkbox']:checked + label::after { + border-color: #d7c863; + } + } } // date-picker -.date-picker{ - svg{display: none;} - .react-datepicker-wrapper{ - width: 100%; +.date-picker { + svg { + display: none; + } + .react-datepicker-wrapper { + width: 100%; + } + input[type='text'] { + display: block; + width: 100%; + height: 30px; + padding: 0 34px 0 10px; + border-radius: 2px; + border: 1px solid #eee; + font-size: 13px; + color: #45576f; + font-weight: normal; + font-family: 'Noto Sans JP', sans-serif; + background: #fff url(../../public/static/images/common/datepicker.svg) calc(100% - 11px) center no-repeat; + background-size: 14px 15px; + cursor: pointer; + } +} + +// react select +.react-select-custom { + width: 100%; + .custom__control { + height: 30px; + min-height: unset; + border-radius: 2px; + border-color: #eee; + background-color: #fff; + &:hover { + border-color: #eee; } - input[type=text]{ - display: block; - width: 100%; - height: 30px; - padding: 0 34px 0 10px; - border-radius: 2px; - border: 1px solid #eee; - font-size: 13px; - color: #45576F; - font-weight: normal; - font-family: 'Noto Sans JP', sans-serif; - background: #fff url(../../public/static/images/common/datepicker.svg) calc(100% - 11px) center no-repeat; - background-size: 14px 15px; - cursor: pointer; + } + .custom__control--is-focused { + border-color: #eee; + box-shadow: unset; + &:hover { + border-color: #eee; } -} \ No newline at end of file + } + .custom__value-container { + height: 100%; + padding: 0 10px; + } + .custom__placeholder, + .custom__single-value { + margin: 0; + } + .custom__single-value { + font-size: 13px; + color: #45576f; + font-weight: 400; + } + .custom__placeholder { + font-size: 13px; + color: #bbbbbb; + font-weight: 400; + } + .custom__clear-indicator, + .custom__indicator-separator { + display: none; + } + .custom__indicator { + padding: 0; + svg { + display: none; + } + } + .custom__dropdown-indicator { + width: 30px; + height: 100%; + background: url(../../public/static/images/common/select_light_arr.svg) no-repeat center; + } + + .custom__menu { + margin: 1px 0; + border-radius: 2px; + overflow: hidden; + } + .custom__menu-list { + padding: 0; + } + .custom__option { + font-size: 13px; + padding: 7px 10px; + color: #45576f; + } + .custom__option--is-selected { + color: #fff; + } + // disable + &.custom--is-disabled { + .custom__control { + background-color: #fafafa; + } + .custom__single-value { + color: #999999; + } + } +} diff --git a/src/styles/_submodal.scss b/src/styles/_submodal.scss index 34d0e9ce..5b18f74a 100644 --- a/src/styles/_submodal.scss +++ b/src/styles/_submodal.scss @@ -71,8 +71,33 @@ } } } + &.community{ + .modal-dialog{ + .modal-content{ + .modal-header{ + padding: 19px 24px; + background-color: #fff; + .modal-close{ + background: url(../../public/static/images/sub/community_pop_close.svg)no-repeat center; + } + } + .modal-body{ + padding: 0 30px 30px; + } + } + } + } } +.explane{ + font-size: 13px; + font-weight: 400; + color: #101010; + margin-bottom: 20px; +} +.red{ + color: #F00; +} // modal-contents // 비밀번호 변경 @@ -186,6 +211,19 @@ } // 설계의뢰 불러오기 +.design-tit-wrap{ + display: flex; + align-items: center; + margin-bottom: 10px; + h3{ + font-size: 13px; + font-weight: 600; + color: #101010; + } + .design-search-wrap{ + margin-left: auto; + } +} .design-request-table{ margin-bottom: 20px; } @@ -196,4 +234,85 @@ color: #101010; margin-bottom: 15px; } +} + +// 제품 특이사항 팝업 +.calculation-estimate{ + &.usemodal{ + margin-bottom: 0; + border: none; + padding: 0; + } +} + +// 내정보 조회 팝업 +.password-input{ + flex: 1; + display: flex; + align-items: center; + height: 30px; + border: 1px solid #EEE; + padding: 0 10px; + border-radius: 2px; + input{ + width: 100%; + height: 100%; + font-size: 13px; + color: #45576F; + font-family: 'Noto Sans JP', sans-serif; + font-weight: 400; + &::placeholder{ + color: #D1D7E0; + } + } + .blink{ + flex: none; + width: 19px; + height: 100%; + background-image: url(../../public/static/images/main/password_hidden.svg); + background-size: 19px 13px; + background-repeat: no-repeat; + background-position: center; + &.on{ + background-image: url(../../public/static/images/main/password_visible.svg); + } + } +} + +// 커뮤니티 +.community_detail{ + .community_detail-tit{ + font-size: 16px; + color: #101010; + font-weight: 600; + padding-bottom: 14px; + border-bottom: 2px solid #101010; + } + .community_detail-file-wrap{ + padding: 24px 0; + border-bottom: 1px solid #E5E5E5; + dt{ + font-size: 13px; + color: #101010; + font-weight: 500; + margin-bottom: 15px; + } + dd{ + font-size: 12px; + font-weight: 400; + margin-bottom: 3px; + color: #344356; + &:last-child{ + margin-bottom: 0; + } + } + } + .community_detail-inner{ + padding-top: 20px; + padding-bottom: 20px; + font-size: 13px; + font-weight: 400; + color: #45576F; + line-height: 26px; + } } \ No newline at end of file diff --git a/src/styles/_table.scss b/src/styles/_table.scss index 5aa068d0..d7e1c407 100644 --- a/src/styles/_table.scss +++ b/src/styles/_table.scss @@ -115,7 +115,40 @@ table{ cursor: pointer; span{ position: absolute; + top: 50%; + transform: translateY(-50%); + left: 25px; + padding: 11px 7px; + font-size: 12px; + font-weight: 400; + color: #45576F; + background-color: #fff; + border: 2px solid #45576F; + border-radius: 2px; white-space: nowrap; + opacity: 0; + visibility: hidden; + z-index: 99; + transition: all .15s ease-in-out; + &::before{ + content: ''; + position: absolute; + top: 50%; + left: -6px; + transform: translateY(-50%) rotate(45deg); + width: 9px; + height: 9px; + border: 2px solid #45576F; + background-color: #fff; + border-top: none; + border-right: none; + } + } + &:hover{ + span{ + opacity: 1; + visibility: visible; + } } } } @@ -279,4 +312,122 @@ table{ } } } +} + +// 커뮤니티 테이블 +.community-table{ + margin-bottom: 24px; + table{ + table-layout: fixed; + border-collapse: collapse; + border-top: 2px solid #101010; + tbody{ + td{ + font-size: 13px; + font-weight: 400; + color: #45576F; + padding: 10.5px 10px; + border-bottom: 1px solid #ECF0F4; + vertical-align: middle; + .text-frame{ + display: flex; + align-items: center; + .text-overflow{ + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + text-overflow: ellipsis; + overflow: hidden; + } + .clip{ + flex: none; + display: block; + margin-left: 10px; + width: 14px; + height: 14px; + background: url(../../public/static/images/sub/community_clip.svg)no-repeat center; + background-size: cover; + } + } + } + tr{ + background-color: transparent; + transition: all .15s ease-in-out; + cursor: pointer; + &:hover{ + background: #F7F9FA; + } + } + } + } +} + +// 풍속 선택 테이블 +.wind-table{ + margin-top: 20px; + table{ + width: 100%; + table-layout: fixed; + border-collapse: separate; + thead{ + display: table; + table-layout: fixed; + width: 100%; + tr{ + th{ + text-align: center; + font-size: 13px; + font-weight: 600; + color: #344356; + background-color: #F7F9FA; + padding: 10.5px 10px; + vertical-align: middle; + border-bottom: 1px solid #ECF0F4; + border-top: 1px solid #ECF0F4; + &:first-child{ + border-left: 1px solid #ECF0F4; + border-radius: 4px 0 0 4px; + } + &:last-child{ + border-right: 1px solid #ECF0F4; + border-radius: 0 4px 4px 0; + } + } + } + } + tbody{ + display: block; + max-height: 200px; + overflow-y: auto; + tr{ + display: table; + table-layout: fixed; + width: 100%; + td{ + padding: 13.5px 10px; + font-size: 13px; + font-weight: 400; + color: #45576F; + vertical-align: middle; + border-bottom: 1px solid #ECF0F4; + &:nth-child(2){ + width: 110px; + } + &:nth-child(1){ + width: 50px; + } + } + } + &::-webkit-scrollbar { + width: 4px; + background-color: transparent; + } + &::-webkit-scrollbar-thumb { + background-color: #ECF0F4; + } + &::-webkit-scrollbar-track { + background-color: transparent; + } + } + } } \ No newline at end of file diff --git a/src/styles/contents.scss b/src/styles/contents.scss index 613c0644..2f690380 100644 --- a/src/styles/contents.scss +++ b/src/styles/contents.scss @@ -2,4 +2,5 @@ @import '_modal.scss'; @import '_submodal.scss'; @import '_table.scss'; -@import '_canvasside.scss'; \ No newline at end of file +@import '_canvasside.scss'; +@import '_pagination.scss'; \ No newline at end of file diff --git a/src/styles/grid.scss b/src/styles/grid.scss index 71f42715..4b20cd8c 100644 --- a/src/styles/grid.scss +++ b/src/styles/grid.scss @@ -1,2 +1 @@ @import '_grid-detail.scss'; -@import '_pagination.scss'; \ No newline at end of file diff --git a/src/util/board-utils.js b/src/util/board-utils.js new file mode 100644 index 00000000..c41ba22a --- /dev/null +++ b/src/util/board-utils.js @@ -0,0 +1,52 @@ +import { useAxios } from '@/hooks/useAxios' + +// 파일 다운로드 +export const handleFileDown = async (file) => { + const { promiseGet } = useAxios() + + const url = `/api/board/file/download` + const params = new URLSearchParams({ + encodeFileNo: file.encodeFileNo, + }) + const options = { responseType: 'blob' } + const apiUrl = `${url}?${params.toString()}` + + await promiseGet({ url: apiUrl, option: options }) + .then((resultData) => { + if (resultData) { + const blob = new Blob([resultData.data], { type: resultData.headers['content-type'] || 'application/octet-stream' }) + const fileUrl = window.URL.createObjectURL(blob) + const link = document.createElement('a') + + link.href = fileUrl + link.download = file.srcFileNm + document.body.appendChild(link) + link.click() + link.remove() + window.URL.revokeObjectURL(fileUrl) + } + }) + .catch((error) => { + alert('File does not exist.') + }) +} + +// 페이지 번호 생성 +export const generateBlockPagination = (currentPage, totalPages, pageBlock) => { + const currentBlock = Math.ceil(currentPage / pageBlock) + + let startPage = (currentBlock - 1) * pageBlock + 1 + let endPage = startPage + pageBlock - 1 + + if (endPage > totalPages) { + endPage = totalPages + } + + let pageArr = [] + + for (let i = startPage; i <= endPage; i++) { + pageArr.push(i) + } + + return pageArr +} diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index 4efd9138..17c30f07 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -1,5 +1,5 @@ import { intersect } from 'mathjs' - +import * as turf from '@turf/turf' /** * Collection of function to use on canvas */ @@ -725,3 +725,169 @@ export const getIntersectionPoint = (p1, p2, y) => { const x = (y - intercept) / slope return { x, y } } + +export const pointsToTurfPolygon = (points) => { + const coordinates = points.map((point) => [point.x, point.y]) + coordinates.push(coordinates[0]) + 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 const splitDormerTriangle = (triangle, direction) => { + const halfWidth = triangle.width / 2 + + let leftPoints = [] + let rightPoints = [] + let leftPointOffset = [] + let rightPointOffset = [] + + if (direction === 'down') { + leftPoints = [ + { x: triangle.left, y: triangle.top }, + { x: triangle.left - halfWidth, y: triangle.top + triangle.height }, + { x: triangle.left, y: triangle.top + triangle.height }, + ] + + rightPoints = [ + { x: triangle.left, y: triangle.top }, + { x: triangle.left, y: triangle.top + triangle.height }, + { x: triangle.left + halfWidth, y: triangle.top + triangle.height }, + ] + } else if (direction === 'up') { + leftPoints = [ + { x: triangle.left, y: triangle.top }, + { x: triangle.left - halfWidth, y: triangle.top - triangle.height }, + { x: triangle.left, y: triangle.top - triangle.height }, + ] + + rightPoints = [ + { x: triangle.left, y: triangle.top }, + { x: triangle.left, y: triangle.top - triangle.height }, + { x: triangle.left + halfWidth, y: triangle.top - triangle.height }, + ] + } else if (direction === 'left') { + leftPoints = [ + { x: triangle.left, y: triangle.top }, + { x: triangle.left - triangle.height, y: triangle.top - halfWidth }, + { x: triangle.left - triangle.height, y: triangle.top }, + ] + + rightPoints = [ + { x: triangle.left, y: triangle.top }, + { x: triangle.left - triangle.height, y: triangle.top }, + { x: triangle.left - triangle.height, y: triangle.top + halfWidth }, + ] + } else if (direction === 'right') { + leftPoints = [ + { x: triangle.left, y: triangle.top }, + { x: triangle.left + triangle.height, y: triangle.top }, + { x: triangle.left + triangle.height, y: triangle.top + triangle.height }, + ] + + rightPoints = [ + { x: triangle.left, y: triangle.top }, + { x: triangle.left + triangle.height, y: triangle.top }, + { x: triangle.left + triangle.height, y: triangle.top - triangle.height }, + ] + } + + return [leftPoints, rightPoints] +} + +export const triangleToPolygon = (triangle) => { + const points = [] + const halfWidth = triangle.width / 2 + const height = triangle.height + + points.push({ x: triangle.left + halfWidth, y: triangle.top }) + points.push({ x: triangle.left, y: triangle.top + height }) + points.push({ x: triangle.left + triangle.width, y: triangle.top + height }) + + return points +} + +export const rectToPolygon = (rect) => { + const points = [] + const left = rect.left + const top = rect.top + const width = rect.width * rect.scaleX // 스케일 적용 + const height = rect.height * rect.scaleY // 스케일 적용 + + // 네 개의 꼭짓점 좌표 계산 + points.push({ x: left, y: top }) // 좌상단 + points.push({ x: left + width, y: top }) // 우상단 + points.push({ x: left + width, y: top + height }) // 우하단 + points.push({ x: left, y: top + height }) // 좌하단 + + return points +} + +//면형상 선택 클릭시 지붕 패턴 입히기 +export function setSurfaceShapePattern(polygon) { + const ratio = window.devicePixelRatio || 1 + + let width = 265 / 10 + let height = 150 / 10 + 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') + const 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 = 'green' + ctx.lineWidth = 0.4 + + 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() + + for (let col = 0; col <= cols; col++) { + const x = col * patternSize.width + (row % 2 === 0 ? 0 : offset) + const yStart = row * patternSize.height + const yEnd = yStart + patternSize.height + + ctx.beginPath() + ctx.moveTo(x, yStart) // 선 시작점 + ctx.lineTo(x, yEnd) // 선 끝점 + ctx.stroke() + } + } + + // 패턴 생성 + const pattern = new fabric.Pattern({ + source: patternSourceCanvas, + repeat: 'repeat', + }) + + polygon.set('fill', null) + polygon.set('fill', pattern) + polygon.canvas?.renderAll() +} diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 62597365..35427940 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -1150,9 +1150,12 @@ export const splitPolygonWithLines = (polygon) => { stroke: 'black', fill: 'transparent', strokeWidth: 3, - name: 'roof', - selectable: false, + name: POLYGON_TYPE.ROOF, + originX: 'center', + originY: 'center', + selectable: true, defense: defense, + direction: defense, }) polygon.canvas.add(roof) @@ -1236,8 +1239,6 @@ const drawRidge = (roof, canvas) => { // 지붕의 길이가 짧은 순으로 정렬 ridgeRoof.sort((a, b) => a.length - b.length) - console.log('ridgeRoof : ', ridgeRoof) - ridgeRoof.forEach((item) => { console.log(getMaxRidge(roofs.length), roof.ridges.length) if (getMaxRidge(roofs.length) > roof.ridges.length) { @@ -1475,7 +1476,6 @@ const drawRidge = (roof, canvas) => { } } - console.log('startXPoint', startXPoint, 'startYPoint', startYPoint, 'endXPoint', endXPoint, 'endYPoint', endYPoint) // 마루 그리기 if (startXPoint !== undefined && startYPoint !== undefined && endXPoint !== undefined && endYPoint !== undefined) { const ridge = new QLine( @@ -3011,7 +3011,7 @@ export const drawDirectionArrow = (polygon) => { polygon.canvas.remove(polygon.arrow) } - let centerPoint = { x: polygon.width / 2 + polygon.left, y: polygon.height / 2 + polygon.top } + let centerPoint = { x: polygon.left, y: polygon.top } let stickeyPoint const polygonMaxX = Math.max(...polygon.getCurrentPoints().map((point) => point.x)) @@ -3022,58 +3022,58 @@ export const drawDirectionArrow = (polygon) => { switch (direction) { case 'north': points = [ - { x: centerPoint.x, y: polygonMinY - 20 }, - { x: centerPoint.x + 20, y: polygonMinY - 20 }, + { x: centerPoint.x, y: polygonMinY - 50 }, { x: centerPoint.x + 20, y: polygonMinY - 50 }, - { x: centerPoint.x + 50, y: polygonMinY - 50 }, - { x: centerPoint.x, y: polygonMinY - 80 }, - { x: centerPoint.x - 50, y: polygonMinY - 50 }, + { x: centerPoint.x + 20, y: polygonMinY - 80 }, + { x: centerPoint.x + 50, y: polygonMinY - 80 }, + { x: centerPoint.x, y: polygonMinY - 110 }, + { x: centerPoint.x - 50, y: polygonMinY - 80 }, + { x: centerPoint.x - 20, y: polygonMinY - 80 }, { x: centerPoint.x - 20, y: polygonMinY - 50 }, - { x: centerPoint.x - 20, y: polygonMinY - 20 }, ] - stickeyPoint = { x: centerPoint.x, y: polygonMinY - 80 } + stickeyPoint = { x: centerPoint.x, y: polygonMinY - 110 } break case 'south': points = [ - { x: centerPoint.x, y: polygonMaxY + 20 }, - { x: centerPoint.x + 20, y: polygonMaxY + 20 }, + { x: centerPoint.x, y: polygonMaxY + 50 }, { x: centerPoint.x + 20, y: polygonMaxY + 50 }, - { x: centerPoint.x + 50, y: polygonMaxY + 50 }, - { x: centerPoint.x, y: polygonMaxY + 80 }, - { x: centerPoint.x - 50, y: polygonMaxY + 50 }, + { x: centerPoint.x + 20, y: polygonMaxY + 80 }, + { x: centerPoint.x + 50, y: polygonMaxY + 80 }, + { x: centerPoint.x, y: polygonMaxY + 110 }, + { x: centerPoint.x - 50, y: polygonMaxY + 80 }, + { x: centerPoint.x - 20, y: polygonMaxY + 80 }, { x: centerPoint.x - 20, y: polygonMaxY + 50 }, - { x: centerPoint.x - 20, y: polygonMaxY + 20 }, ] - stickeyPoint = { x: centerPoint.x, y: polygonMaxY + 80 } + stickeyPoint = { x: centerPoint.x, y: polygonMaxY + 110 } break case 'west': points = [ - { x: polygonMinX - 20, y: centerPoint.y }, - { x: polygonMinX - 20, y: centerPoint.y + 20 }, + { x: polygonMinX - 50, y: centerPoint.y }, { x: polygonMinX - 50, y: centerPoint.y + 20 }, - { x: polygonMinX - 50, y: centerPoint.y + 50 }, - { x: polygonMinX - 80, y: centerPoint.y }, - { x: polygonMinX - 50, y: centerPoint.y - 50 }, + { x: polygonMinX - 80, y: centerPoint.y + 20 }, + { x: polygonMinX - 80, y: centerPoint.y + 50 }, + { x: polygonMinX - 110, y: centerPoint.y }, + { x: polygonMinX - 80, y: centerPoint.y - 50 }, + { x: polygonMinX - 80, y: centerPoint.y - 20 }, { x: polygonMinX - 50, y: centerPoint.y - 20 }, - { x: polygonMinX - 20, y: centerPoint.y - 20 }, ] - stickeyPoint = { x: polygonMinX - 80, y: centerPoint.y } + stickeyPoint = { x: polygonMinX - 110, y: centerPoint.y } break case 'east': points = [ - { x: polygonMaxX + 20, y: centerPoint.y }, - { x: polygonMaxX + 20, y: centerPoint.y + 20 }, + { x: polygonMaxX + 50, y: centerPoint.y }, { x: polygonMaxX + 50, y: centerPoint.y + 20 }, - { x: polygonMaxX + 50, y: centerPoint.y + 50 }, - { x: polygonMaxX + 80, y: centerPoint.y }, - { x: polygonMaxX + 50, y: centerPoint.y - 50 }, + { x: polygonMaxX + 80, y: centerPoint.y + 20 }, + { x: polygonMaxX + 80, y: centerPoint.y + 50 }, + { x: polygonMaxX + 110, y: centerPoint.y }, + { x: polygonMaxX + 80, y: centerPoint.y - 50 }, + { x: polygonMaxX + 80, y: centerPoint.y - 20 }, { x: polygonMaxX + 50, y: centerPoint.y - 20 }, - { x: polygonMaxX + 20, y: centerPoint.y - 20 }, ] - stickeyPoint = { x: polygonMaxX + 80, y: centerPoint.y } + stickeyPoint = { x: polygonMaxX + 110, y: centerPoint.y } break } diff --git a/yarn.lock b/yarn.lock index 1ffd6b98..c8c53b57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -216,7 +216,7 @@ dependencies: "@babel/types" "^7.25.7" -"@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3": +"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": version "7.25.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.7.tgz#7ffb53c37a8f247c8c4d335e89cdf16a2e0d0fb6" integrity sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w== @@ -273,7 +273,7 @@ resolved "https://registry.npmjs.org/@bedrock-layout/use-stateful-ref/-/use-stateful-ref-1.4.1.tgz" integrity sha512-4eKO2KdQEXcR5LI4QcxqlJykJUDQJWDeWYAukIn6sRQYoabcfI5kDl61PUi6FR6o8VFgQ8IEP7HleKqWlSe8SQ== -"@emotion/babel-plugin@^11.11.0": +"@emotion/babel-plugin@^11.12.0": version "11.12.0" resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz#7b43debb250c313101b3f885eba634f1d723fcc2" integrity sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw== @@ -290,7 +290,7 @@ source-map "^0.5.7" stylis "4.2.0" -"@emotion/cache@^11.11.0": +"@emotion/cache@^11.13.0", "@emotion/cache@^11.4.0": version "11.13.1" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.13.1.tgz#fecfc54d51810beebf05bf2a161271a1a91895d7" integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw== @@ -306,33 +306,26 @@ resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.2.tgz#ff9221b9f58b4dfe61e619a7788734bd63f6898b" integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== -"@emotion/is-prop-valid@^1.2.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz#8d5cf1132f836d7adbe42cf0b49df7816fc88240" - integrity sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw== - dependencies: - "@emotion/memoize" "^0.9.0" - "@emotion/memoize@^0.9.0": version "0.9.0" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102" integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== -"@emotion/react@11.11.0": - version "11.11.0" - resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.0.tgz#408196b7ef8729d8ad08fc061b03b046d1460e02" - integrity sha512-ZSK3ZJsNkwfjT3JpDAWJZlrGD81Z3ytNDsxw1LKq1o+xkmO5pnWfr6gmCC8gHEFf3nSSX/09YrG67jybNPxSUw== +"@emotion/react@^11.8.1": + version "11.13.3" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.13.3.tgz#a69d0de2a23f5b48e0acf210416638010e4bd2e4" + integrity sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg== dependencies: "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.11.0" - "@emotion/cache" "^11.11.0" - "@emotion/serialize" "^1.1.2" - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" - "@emotion/utils" "^1.2.1" - "@emotion/weak-memoize" "^0.3.1" + "@emotion/babel-plugin" "^11.12.0" + "@emotion/cache" "^11.13.0" + "@emotion/serialize" "^1.3.1" + "@emotion/use-insertion-effect-with-fallbacks" "^1.1.0" + "@emotion/utils" "^1.4.0" + "@emotion/weak-memoize" "^0.4.0" hoist-non-react-statics "^3.3.1" -"@emotion/serialize@^1.1.2", "@emotion/serialize@^1.2.0": +"@emotion/serialize@^1.2.0", "@emotion/serialize@^1.3.1": version "1.3.2" resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.3.2.tgz#e1c1a2e90708d5d85d81ccaee2dfeb3cc0cccf7a" integrity sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA== @@ -348,38 +341,21 @@ resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c" integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== -"@emotion/styled@11.11.0": - version "11.11.0" - resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.11.0.tgz#26b75e1b5a1b7a629d7c0a8b708fbf5a9cdce346" - integrity sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng== - dependencies: - "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.11.0" - "@emotion/is-prop-valid" "^1.2.1" - "@emotion/serialize" "^1.1.2" - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" - "@emotion/utils" "^1.2.1" - "@emotion/unitless@^0.10.0": version "0.10.0" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.10.0.tgz#2af2f7c7e5150f497bdabd848ce7b218a27cf745" integrity sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg== -"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": +"@emotion/use-insertion-effect-with-fallbacks@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz#1a818a0b2c481efba0cf34e5ab1e0cb2dcb9dfaf" integrity sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw== -"@emotion/utils@^1.2.1", "@emotion/utils@^1.4.0", "@emotion/utils@^1.4.1": +"@emotion/utils@^1.4.0", "@emotion/utils@^1.4.1": version "1.4.1" resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.1.tgz#b3adbb43de12ee2149541c4f1337d2eb7774f0ad" integrity sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA== -"@emotion/weak-memoize@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" - integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== - "@emotion/weak-memoize@^0.4.0": version "0.4.0" resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6" @@ -400,6 +376,14 @@ "@floating-ui/core" "^1.6.0" "@floating-ui/utils" "^0.2.7" +"@floating-ui/dom@^1.0.1": + version "1.6.11" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.11.tgz#8631857838d34ee5712339eb7cbdfb8ad34da723" + integrity sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ== + dependencies: + "@floating-ui/core" "^1.6.0" + "@floating-ui/utils" "^0.2.8" + "@floating-ui/react-dom@^2.1.1": version "2.1.1" resolved "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz" @@ -421,6 +405,11 @@ resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.7.tgz" integrity sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA== +"@floating-ui/utils@^0.2.8": + version "0.2.8" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62" + integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig== + "@formatjs/ecma402-abstract@2.0.0": version "2.0.0" resolved "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz" @@ -4085,6 +4074,26 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== +"@types/prop-types@*": + version "15.7.13" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.13.tgz#2af91918ee12d9d32914feb13f5326658461b451" + integrity sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA== + +"@types/react-transition-group@^4.4.0": + version "4.4.11" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.11.tgz#d963253a611d757de01ebb241143b1017d5d63d5" + integrity sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA== + dependencies: + "@types/react" "*" + +"@types/react@*": + version "18.3.11" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.11.tgz#9d530601ff843ee0d7030d4227ea4360236bd537" + integrity sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + "@types/readable-stream@^4.0.0": version "4.0.15" resolved "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.15.tgz" @@ -4677,6 +4686,14 @@ dlv@^1.1.3: resolved "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz" integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + domexception@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz" @@ -5189,6 +5206,11 @@ jiti@^1.21.0: resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz" integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w== +js-cookie@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc" + integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw== + js-md4@^0.3.2: version "0.3.2" resolved "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz" @@ -5416,6 +5438,11 @@ mathjs@^13.0.2: tiny-emitter "^2.1.0" typed-function "^4.2.1" +memoize-one@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" + integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== + merge2@^1.3.0: version "1.4.1" resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" @@ -5795,7 +5822,7 @@ process@^0.11.10: resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== -prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -5890,14 +5917,6 @@ react-draggable@^4.4.6: clsx "^1.1.1" prop-types "^15.8.1" -react-dropdown-select@^4.11.3: - version "4.11.3" - resolved "https://registry.yarnpkg.com/react-dropdown-select/-/react-dropdown-select-4.11.3.tgz#b23b8906f3bedc9d6a1a2125af936b34d4057158" - integrity sha512-/mOGSqqhmKsxxrmotLM+qn1Ss3nxGN6QnYusyQ7f0wizsWrc7ZmbcZhGRtwkJwpL6JYDQVTn19EYxJU1XfXrDA== - dependencies: - "@emotion/react" "11.11.0" - "@emotion/styled" "11.11.0" - react-hook-form@^7.53.0: version "7.53.0" resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.53.0.tgz#3cf70951bf41fa95207b34486203ebefbd3a05ab" @@ -5946,6 +5965,21 @@ react-responsive-modal@^6.4.2: body-scroll-lock "^3.1.5" classnames "^2.3.1" +react-select@^5.8.1: + version "5.8.1" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.8.1.tgz#3284a93b7633b5e893306b2a8007ea0f793e62b9" + integrity sha512-RT1CJmuc+ejqm5MPgzyZujqDskdvB9a9ZqrdnVLsvAHjJ3Tj0hELnLeVPQlmYdVKCdCpxanepl6z7R5KhXhWzg== + dependencies: + "@babel/runtime" "^7.12.0" + "@emotion/cache" "^11.4.0" + "@emotion/react" "^11.8.1" + "@floating-ui/dom" "^1.0.1" + "@types/react-transition-group" "^4.4.0" + memoize-one "^6.0.0" + prop-types "^15.6.0" + react-transition-group "^4.3.0" + use-isomorphic-layout-effect "^1.1.2" + react-style-singleton@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz" @@ -5971,6 +6005,16 @@ react-toastify@^10.0.5: dependencies: clsx "^2.1.0" +react-transition-group@^4.3.0: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@^18: version "18.3.1" resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz" @@ -6544,7 +6588,7 @@ use-composed-ref@^1.3.0: resolved "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz" integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ== -use-isomorphic-layout-effect@^1.1.1: +use-isomorphic-layout-effect@^1.1.1, use-isomorphic-layout-effect@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz" integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==