Merge branch 'dev' into feature/test-jy

# Conflicts:
#	src/util/qpolygon-utils.js
This commit is contained in:
Jaeyoung Lee 2024-10-18 10:33:54 +09:00
commit fd92766a4f
126 changed files with 9917 additions and 3195 deletions

View File

@ -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"
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"

View File

@ -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"
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"

View File

@ -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"
}

View File

@ -0,0 +1,4 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="14" height="14" rx="7" stroke="white"/>
<path d="M9.741 10.559L7.509 8.327L5.277 10.559L4.512 9.794L6.744 7.562L4.512 5.33L5.277 4.565L7.509 6.797L9.741 4.565L10.506 5.33L8.274 7.562L10.506 9.794L9.741 10.559Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 358 B

View File

@ -1,5 +1,5 @@
<svg width="154" height="155" viewBox="0 0 154 155" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_4103_3996)">
<svg width="154" height="140" viewBox="0 0 154 140" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_24_15)">
<path d="M1.65636 139L70 9.14709L138.344 139H1.65636Z" stroke="black" stroke-width="2"/>
<rect x="69" y="11" width="2" height="127" fill="black"/>
<circle cx="70" cy="8" r="7.5" stroke="#FF0000"/>
@ -9,13 +9,8 @@
<path d="M144 134L148.5 138L153 134" stroke="black"/>
<rect x="144" width="9" height="1" fill="#FF0000"/>
<rect x="144" y="139" width="9" height="1" fill="#FF0000"/>
<rect x="2" y="150" width="1" height="136" transform="rotate(-90 2 150)" fill="black"/>
<path d="M6 154L2 149.5L6 145" stroke="black"/>
<path d="M134 154L138 149.5L134 145" stroke="black"/>
<rect y="154" width="9" height="1" transform="rotate(-90 0 154)" fill="#FF0000"/>
<rect x="139" y="154" width="9" height="1" transform="rotate(-90 139 154)" fill="#FF0000"/>
<defs>
<clipPath id="clip0_4103_3996">
<clipPath id="clip0_24_15">
<rect width="140" height="140" fill="white"/>
</clipPath>
</defs>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 716 B

View File

@ -0,0 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="elements">
<path id="Vector" d="M6.05566 9.94445L9.94455 6.05556" stroke="#45576F" stroke-width="1.5" stroke-linecap="round"/>
<path id="Vector_2" d="M11.7693 10.0296L13.799 8C15.4003 6.39865 15.4003 3.80236 13.799 2.20101C12.1976 0.599663 9.60135 0.599663 8 2.20101L5.97035 4.23066M10.0296 11.7693L8 13.799C6.39865 15.4003 3.80236 15.4003 2.20101 13.799C0.599663 12.1976 0.599663 9.60135 2.20101 8L4.23066 5.97035" stroke="#45576F" stroke-width="1.5" stroke-linecap="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 592 B

View File

@ -0,0 +1,5 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="elements">
<path id="Vector" d="M8.99915 9L1 1M1.00085 9L9 1" stroke="#101010" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 260 B

View File

@ -0,0 +1,11 @@
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="element" clip-path="url(#clip0_4262_4496)">
<path id="&#237;&#140;&#168;&#236;&#138;&#164; 171" d="M17.879 17.9735L19.879 19.9735" stroke="#101010" stroke-width="1.5" stroke-linecap="round"/>
<path id="Vector" d="M9.98901 18.4039C14.6834 18.4039 18.489 14.5989 18.489 9.90519C18.489 5.21149 14.6834 1.40649 9.98901 1.40649C5.29459 1.40649 1.48901 5.21149 1.48901 9.90519C1.48901 14.5989 5.29459 18.4039 9.98901 18.4039Z" stroke="#101010" stroke-width="1.5"/>
</g>
<defs>
<clipPath id="clip0_4262_4496">
<rect width="21" height="21" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 677 B

View File

@ -0,0 +1,6 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="35" height="35" rx="17.5" fill="white"/>
<rect x="0.5" y="0.5" width="35" height="35" rx="17.5" stroke="#45576F"/>
<path d="M9 20L9.23384 20.6626C10.144 23.2413 10.5991 24.5307 11.6374 25.2654C12.6758 26 14.0431 26 16.7778 26H19.2222C21.9569 26 23.3242 26 24.3626 25.2654C25.4009 24.5307 25.856 23.2413 26.7662 20.6626L27 20" stroke="#45576F" stroke-width="1.5" stroke-linecap="round"/>
<path d="M18 20L18 10M18 20C17.2998 20 15.9915 18.0057 15.5 17.5M18 20C18.7002 20 20.0085 18.0057 20.5 17.5" stroke="#45576F" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 713 B

View File

@ -0,0 +1,6 @@
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="elements">
<path id="Vector 4232" d="M0.499939 8.5556L0.681818 9.07093C1.38972 11.0766 1.74367 12.0795 2.55127 12.6509C3.35887 13.2223 4.42235 13.2223 6.54932 13.2223H8.45058C10.5775 13.2223 11.641 13.2223 12.4486 12.6509C13.2562 12.0795 13.6102 11.0766 14.3181 9.07093L14.5 8.5556" stroke="#94A0AD" stroke-linecap="round"/>
<path id="Vector" d="M7.49981 8.55556V0.777771M7.49981 8.55556C6.95518 8.55556 5.93766 7.00444 5.55536 6.61111M7.49981 8.55556C8.04443 8.55556 9.06195 7.00444 9.44425 6.61111" stroke="#94A0AD" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 682 B

View File

@ -0,0 +1,8 @@
<svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="elements">
<path id="Rectangle 2175" d="M12.5556 6.25V5.41C12.5497 3.44799 12.487 2.40784 11.8095 1.73809C11.0629 1 9.8612 1 7.45783 1L6.09832 1C3.69495 1 2.49326 1 1.74663 1.73809C1 2.47618 1 3.66412 1 6.04L1 9.26C1 11.6359 1 12.8238 1.74663 13.5619C2.40945 14.2171 3.4309 14.2907 5.33333 14.299" stroke="#94A0AD" stroke-linecap="round"/>
<path id="Vector" d="M12.321 13.382L13.9999 15M13.2777 11.15C13.2777 9.4103 11.8226 8 10.0277 8C8.23278 8 6.77771 9.4103 6.77771 11.15C6.77771 12.8897 8.23278 14.3 10.0277 14.3C11.8226 14.3 13.2777 12.8897 13.2777 11.15Z" stroke="#94A0AD" stroke-linecap="round" stroke-linejoin="round"/>
<path id="Vector 4435" d="M3.88879 4.5H9.66657" stroke="#94A0AD" stroke-linecap="round"/>
<path id="Vector 4436" d="M3.88879 7.29993H6.05546" stroke="#94A0AD" stroke-linecap="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 927 B

View File

@ -0,0 +1,4 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="7" cy="7" r="6.5" fill="white" stroke="#F16A6A"/>
<path d="M6.507 8.728L6.276 4.669L6.221 3.129H7.849L7.794 4.669L7.563 8.728H6.507ZM7.035 11.654C6.74167 11.654 6.49967 11.5513 6.309 11.346C6.12567 11.1407 6.034 10.8913 6.034 10.598C6.034 10.29 6.12567 10.037 6.309 9.839C6.49967 9.641 6.74167 9.542 7.035 9.542C7.32833 9.542 7.56667 9.641 7.75 9.839C7.94067 10.037 8.036 10.29 8.036 10.598C8.036 10.8913 7.94067 11.1407 7.75 11.346C7.56667 11.5513 7.32833 11.654 7.035 11.654Z" fill="#F16A6A"/>
</svg>

After

Width:  |  Height:  |  Size: 611 B

View File

@ -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 <SessionContext.Provider value={{ session }}>{children}</SessionContext.Provider>
}

View File

@ -7,10 +7,7 @@ export default async function CommunityArchivePage() {
return (
<>
<Hero title="자료 다운로드" />
<div className="container flex flex-wrap items-center justify-between mx-auto p-4 m-4 border">
<Archive />
</div>
<Archive />
</>
)
}

View File

@ -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 (
<>
<Hero title="FAQ" />
<div className="container flex flex-wrap items-center justify-between mx-auto p-4 m-4 border">
<Faq />
</div>
<Faq />
</>
)
}

View File

@ -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 (
<>
<Hero title="공지사항" />
<div className="container flex flex-wrap items-center justify-between mx-auto p-4 m-4 border">
<Notice />
</div>
<Notice />
</>
)
}

View File

@ -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 (
<>
<div className="flex min-h-full flex-1 flex-col justify-center px-6 py-12 lg:px-8">
<h1 className="text-center text-4xl font-bold mb-10">{getMessage('join.complete.title')}</h1>
<div className="mt-10 mb-10 w-full text-center text-2xl">{getMessage('join.complete.contents')}</div>
<div className="mt-10 w-full text-center">
{getMessage('join.complete.email_comment')} :&nbsp;{getMessage('join.complete.email')}
</div>
</div>
<JoinComplete />
</>
)
}

View File

@ -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 }) {
<RecoilRootWrapper>
<html lang="en">
<body>
{headerPathname !== '/login' ? (
{headerPathname === '/login' || headerPathname === '/join' ? (
<QcastProvider>{children}</QcastProvider>
) : (
<div className="wrap">
<Header userSession={sessionProps} />
<UIProvider>
<div className="content">
<Dimmed />
<QcastProvider>{children}</QcastProvider>
<div className="content">
<Dimmed />
<QcastProvider>
<SessionProvider useSession={sessionProps}>{children}</SessionProvider>
</QcastProvider>
</div>
<footer>
<div className="footer-inner">
<span>COPYRIGHT©2024 Hanwha Japan All Rights Reserved.</span>
</div>
</UIProvider>
</footer>
</div>
) : (
<QcastProvider>{children}</QcastProvider>
)}
<ToastContainer />
{/* <QModal /> */}
<QModal />
</body>
</html>
</RecoilRootWrapper>

View File

@ -1,10 +1,9 @@
import Login from '@/components/auth/Login'
import NewLogin from '@/components/auth/NewLogin'
export default function LoginPage() {
return (
<>
<NewLogin />
<Login />
</>
)
}

View File

@ -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',
}

View File

@ -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')
}
}

View File

@ -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 (
<>
<div className="container mx-auto p-4 m-4 border">
@ -290,6 +308,35 @@ export default function Playground() {
Sweetalert - confirm
</Button>
</div>
<div className="my-2">
<QPagination {...paginationProps} />
</div>
<div className="my-2">
<Button
onClick={() => {
promiseGet({ url: 'http://localhost:8080/api/user' }).then((res) => setUsers(res.data))
}}
>
axios get test
</Button>
</div>
<div className="my-2">
<Button
onClick={() => {
const result = promisePost({
url: 'http://localhost:8080/api/user',
data: {
firstName: 'Yoo',
lastName: 'Sangwook',
email: 'yoo1757@naver.com',
age: 46,
},
}).then((res) => console.log('res', res))
}}
>
axios post test
</Button>
</div>
</div>
</>
)

View File

@ -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)
}

View File

@ -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 (
<div className="flex min-h-full flex-1 flex-col justify-center px-6 py-12 lg:px-8">
<h1 className="text-center text-4xl font-bold">{getMessage('join.title')}</h1>
<form action={joinProcess}>
<div className="mt-10">
<div>
{getMessage('join.sub1.title')} (*{getMessage('common.require')}) <span>{getMessage('join.sub1.comment')}</span>
<div className="center-page-wrap">
<div className="center-page-inner">
<form onSubmit={joinProcess}>
<div className="center-page-tit">{getMessage('join.title')}</div>
<div className="sub-table-box signup">
<div className="table-box-title-wrap">
<div className="title-wrap">
<h3>
{getMessage('join.sub1.title')} <span className="important">(*{getMessage('common.require')})</span>
</h3>
<span className="option">{getMessage('join.sub1.comment')}</span>
</div>
</div>
<div className="common-table">
<table>
<colgroup>
<col style={{ width: '180px' }} />
<col />
</colgroup>
<tbody>
<tr>
<th>
{getMessage('join.sub1.storeQcastNm')} <span className="important">*</span>
</th>
<td>
<div className="input-wrap" style={{ width: '700px' }}>
<input
type="text"
id="storeQcastNm"
name="storeQcastNm"
required
alt={getMessage('join.sub1.storeQcastNm')}
className="input-light"
placeholder={getMessage('join.sub1.storeQcastNm_placeholder')}
/>
</div>
</td>
</tr>
<tr>
<th>
{getMessage('join.sub1.storeQcastNmKana')} <span className="important">*</span>
</th>
<td>
<div className="input-wrap" style={{ width: '700px' }}>
<input
type="text"
id="storeQcastNmKana"
name="storeQcastNmKana"
required
className="input-light"
placeholder={getMessage('join.sub1.storeQcastNmKana_placeholder')}
/>
</div>
</td>
</tr>
<tr>
<th>
{getMessage('join.sub1.postCd')}/{getMessage('join.sub1.addr')} <span className="important">*</span>
</th>
<td>
<div className="flx-box">
<div className="input-wrap mr5" style={{ width: '200px' }}>
<input
type="text"
id="postCd"
name="postCd"
required
className="input-light"
placeholder={getMessage('join.sub1.postCd_placeholder')}
/>
</div>
<div className="input-wrap" style={{ width: '495px' }}>
<input
type="text"
id="addr"
name="addr"
required
className="input-light"
placeholder={getMessage('join.sub1.addr_placeholder')}
/>
</div>
</div>
</td>
</tr>
<tr>
<th>
{getMessage('join.sub1.telNo')} <span className="important">*</span>
</th>
<td>
<div className="input-wrap" style={{ width: '200px' }}>
<input
type="text"
id="telNo"
name="telNo"
required
className="input-light"
placeholder={getMessage('join.sub1.telNo_placeholder')}
></input>
</div>
</td>
</tr>
<tr>
<th>
{getMessage('join.sub1.fax')} <span className="important">*</span>
</th>
<td>
<div className="input-wrap" style={{ width: '200px' }}>
<input
type="text"
id="fax"
name="fax"
required
className="input-light"
placeholder={getMessage('join.sub1.fax_placeholder')}
></input>
</div>
</td>
</tr>
<tr>
<th>{getMessage('join.sub1.bizNo')}</th>
<td>
<div className="input-wrap" style={{ width: '200px' }}>
<input type="text" id="bizNo" name="bizNo" className="input-light" />
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<table className="w-full">
<colgroup>
<col style={{ width: '20%' }} />
<col style={{ width: '80%' }} />
</colgroup>
<tbody>
<tr>
<th style={{ border: '1px solid gray', padding: '5px' }}>{getMessage('join.sub1.storeQcastNm')} *</th>
<td style={{ border: '1px solid gray', padding: '5px' }}>
<input
type="text"
id="storeQcastNm"
name="storeQcastNm"
required
alt={getMessage('join.sub1.storeQcastNm')}
className="block w-full border-0 py-1.5 ring-1 ring-inset ring-gray-300"
placeholder={getMessage('join.sub1.storeQcastNm_placeholder')}
></input>
</td>
</tr>
<tr>
<th style={{ border: '1px solid gray', padding: '5px' }}>{getMessage('join.sub1.storeQcastNmKana')} *</th>
<td style={{ border: '1px solid gray', padding: '5px' }}>
<input
type="text"
id="storeQcastNmKana"
name="storeQcastNmKana"
required
className="block w-full border-0 py-1.5 ring-1 ring-inset ring-gray-300"
placeholder={getMessage('join.sub1.storeQcastNmKana_placeholder')}
></input>
</td>
</tr>
<tr>
<th style={{ border: '1px solid gray', padding: '5px' }}>
{getMessage('join.sub1.postCd')}/{getMessage('join.sub1.addr')} *
</th>
<td style={{ border: '1px solid gray', padding: '5px' }}>
<input
type="text"
id="postCd"
name="postCd"
required
className="block border-0 py-1.5 ring-1 ring-inset ring-gray-300"
placeholder={getMessage('join.sub1.postCd_placeholder')}
></input>
<input
type="text"
id="addr"
name="addr"
required
className="block w-full border-0 py-1.5 ring-1 ring-inset ring-gray-300"
placeholder={getMessage('join.sub1.addr_placeholder')}
></input>
</td>
</tr>
<tr>
<th style={{ border: '1px solid gray', padding: '5px' }}>{getMessage('join.sub1.telNo')} *</th>
<td style={{ border: '1px solid gray', padding: '5px' }}>
<input
type="text"
id="telNo"
name="telNo"
required
className="block w-full border-0 py-1.5 ring-1 ring-inset ring-gray-300"
placeholder={getMessage('join.sub1.telNo_placeholder')}
></input>
</td>
</tr>
<tr>
<th style={{ border: '1px solid gray', padding: '5px' }}>{getMessage('join.sub1.fax')} *</th>
<td style={{ border: '1px solid gray', padding: '5px' }}>
<input
type="text"
id="fax"
name="fax"
required
className="block w-full border-0 py-1.5 ring-1 ring-inset ring-gray-300"
placeholder={getMessage('join.sub1.fax_placeholder')}
></input>
</td>
</tr>
</tbody>
</table>
<div className="mt-5">
{getMessage('join.sub2.title')} (*{getMessage('common.require')})
<div className="sub-table-box signup">
<div className="table-box-title-wrap">
<div className="title-wrap">
<h3>
{getMessage('join.sub2.title')} <span className="important">(*{getMessage('common.require')})</span>
</h3>
</div>
</div>
<div className="common-table">
<table>
<colgroup>
<col style={{ width: '180px' }} />
<col />
</colgroup>
<tbody>
<tr>
<th>
{getMessage('join.sub2.userNm')} <span className="important">*</span>
</th>
<td>
<div className="input-wrap" style={{ width: '200px' }}>
<input type="text" id="userNm" name="userNm" className="input-light" required />
</div>
</td>
</tr>
<tr>
<th>{getMessage('join.sub2.userNmKana')}</th>
<td>
<div className="input-wrap" style={{ width: '200px' }}>
<input type="text" id="userNmKana" name="userNmKana" className="input-light" />
</div>
</td>
</tr>
<tr>
<th>
{getMessage('join.sub2.userId')} <span className="important">*</span>
</th>
<td>
<div className="input-wrap" style={{ width: '200px' }}>
<input type="text" id="userId" name="userId" className="input-light" required />
</div>
</td>
</tr>
<tr>
<th>
{getMessage('join.sub2.email')} <span className="important">*</span>
</th>
<td>
<div className="input-wrap" style={{ width: '200px' }}>
<input type="text" id="email" name="email" className="input-light" required />
</div>
</td>
</tr>
<tr>
<th>
{getMessage('join.sub2.telNo')} <span className="important">*</span>
</th>
<td>
<div className="input-wrap" style={{ width: '200px' }}>
<input
type="text"
id="userTelNo"
name="userTelNo"
className="input-light"
placeholder={getMessage('join.sub2.telNo_placeholder')}
required
/>
</div>
</td>
</tr>
<tr>
<th>
{getMessage('join.sub2.fax')} <span className="important">*</span>
</th>
<td>
<div className="input-wrap" style={{ width: '200px' }}>
<input
type="text"
id="userFax"
name="userFax"
className="input-light"
placeholder={getMessage('join.sub1.fax_placeholder')}
required
/>
</div>
</td>
</tr>
<tr>
<th>{getMessage('join.sub2.category')}</th>
<td>
<div className="input-wrap" style={{ width: '200px' }}>
<input type="text" id="category" name="category" className="input-light" />
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<table className="w-full">
<colgroup>
<col style={{ width: '20%' }} />
<col style={{ width: '80%' }} />
</colgroup>
<tbody>
<tr>
<th style={{ border: '1px solid gray', padding: '5px' }}>{getMessage('join.sub2.userNm')} *</th>
<td style={{ border: '1px solid gray', padding: '5px' }}>
<input
type="text"
id="userNm"
name="userNm"
required
className="block w-full border-0 py-1.5 ring-1 ring-inset ring-gray-300"
></input>
</td>
</tr>
<tr>
<th style={{ border: '1px solid gray', padding: '5px' }}>{getMessage('join.sub2.userNmKana')} *</th>
<td style={{ border: '1px solid gray', padding: '5px' }}>
<input
type="text"
id="userNmKana"
name="userNmKana"
required
className="block w-full border-0 py-1.5 ring-1 ring-inset ring-gray-300"
></input>
</td>
</tr>
<tr>
<th style={{ border: '1px solid gray', padding: '5px' }}>{getMessage('join.sub2.userId')} *</th>
<td style={{ border: '1px solid gray', padding: '5px' }}>
<input
type="text"
id="userId"
name="userId"
required
className="block w-full border-0 py-1.5 ring-1 ring-inset ring-gray-300"
></input>
</td>
</tr>
<tr>
<th style={{ border: '1px solid gray', padding: '5px' }}>{getMessage('join.sub2.email')} *</th>
<td style={{ border: '1px solid gray', padding: '5px' }}>
<input
type="email"
id="email"
name="email"
required
className="block w-full border-0 py-1.5 ring-1 ring-inset ring-gray-300"
autoComplete="email"
></input>
</td>
</tr>
<tr>
<th style={{ border: '1px solid gray', padding: '5px' }}>{getMessage('join.sub2.telNo')} *</th>
<td style={{ border: '1px solid gray', padding: '5px' }}>
<input
type="text"
id="userTelNo"
name="userTelNo"
required
className="block w-full border-0 py-1.5 ring-1 ring-inset ring-gray-300"
placeholder={getMessage('join.sub2.telNo_placeholder')}
></input>
</td>
</tr>
<tr>
<th style={{ border: '1px solid gray', padding: '5px' }}>{getMessage('join.sub2.fax')} *</th>
<td style={{ border: '1px solid gray', padding: '5px' }}>
<input
type="text"
id="userFax"
name="userFax"
required
className="block w-full border-0 py-1.5 ring-1 ring-inset ring-gray-300"
placeholder={getMessage('join.sub1.fax_placeholder')}
></input>
</td>
</tr>
<tr>
<th style={{ border: '1px solid gray', padding: '5px' }}>{getMessage('join.sub2.category')}</th>
<td style={{ border: '1px solid gray', padding: '5px' }}>
<input id="category" name="category" className="block w-full border-0 py-1.5 ring-1 ring-inset ring-gray-300"></input>
</td>
</tr>
</tbody>
</table>
<div className="mt-5">
{getMessage('join.sub3.title')} (*{getMessage('common.require')})
<div className="sign-up-btn-wrap">
<button
type="button"
className="btn-origin grey mr5"
onClick={() => {
router.push('/login')
}}
>
{getMessage('join.btn.login_page')}
</button>
<button type="submit" className="btn-origin navy">
{getMessage('join.btn.approval_request')}
</button>
</div>
<table className="w-full">
<colgroup>
<col style={{ width: '20%' }} />
<col style={{ width: '80%' }} />
</colgroup>
<tbody>
<tr>
<th style={{ border: '1px solid gray', padding: '5px' }}>{getMessage('join.sub3.qtCompNm')}</th>
<td style={{ border: '1px solid gray', padding: '5px' }}>
<input type="text" id="qtCompNm" name="qtCompNm" className="block w-full border-0 py-1.5 ring-1 ring-inset ring-gray-300"></input>
</td>
</tr>
<tr>
<th style={{ border: '1px solid gray', padding: '5px' }}>
{getMessage('join.sub3.qtPostCd')}/{getMessage('join.sub3.qtAddr')}
</th>
<td style={{ border: '1px solid gray', padding: '5px' }}>
<input
type="text"
id="qtPostCd"
name="qtPostCd"
className="block border-0 py-1.5 ring-1 ring-inset ring-gray-300"
placeholder={getMessage('join.sub3.qtPostCd_placeholder')}
></input>
<input
type="text"
id="qtAddr"
name="qtAddr"
className="block w-full border-0 py-1.5 ring-1 ring-inset ring-gray-300"
placeholder={getMessage('join.sub3.qtAddr_placeholder')}
></input>
</td>
</tr>
<tr>
<th style={{ border: '1px solid gray', padding: '5px' }}>{getMessage('join.sub3.qtEmail')}</th>
<td style={{ border: '1px solid gray', padding: '5px' }}>
<input
type="email"
id="qtEmail"
name="qtEmail"
className="block w-full border-0 py-1.5 ring-1 ring-inset ring-gray-300"
autoComplete="email"
></input>
</td>
</tr>
<tr>
<th style={{ border: '1px solid gray', padding: '5px' }}>{getMessage('join.sub3.qtTelNo')}</th>
<td style={{ border: '1px solid gray', padding: '5px' }}>
<input
type="text"
id="qtTelNo"
name="qtTelNo"
className="block w-full border-0 py-1.5 ring-1 ring-inset ring-gray-300"
placeholder={getMessage('join.sub3.qtTelNo_placeholder')}
></input>
</td>
</tr>
<tr>
<th style={{ border: '1px solid gray', padding: '5px' }}>{getMessage('join.sub3.qtFax')}</th>
<td style={{ border: '1px solid gray', padding: '5px' }}>
<input
type="text"
id="qtFax"
name="qtFax"
className="block w-full border-0 py-1.5 ring-1 ring-inset ring-gray-300"
placeholder={getMessage('join.sub3.qtFax_placeholder')}
></input>
</td>
</tr>
</tbody>
</table>
</div>
<div className="mt-10 mb-10">
<button
type="submit"
className="w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
{getMessage('join.btn.approval_request')}
</button>
</div>
</form>
</form>
</div>
</div>
)
}

View File

@ -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 (
<>
<div className="center-page-wrap">
<div className="center-page-inner complete">
<div className="sub-table-box">
<div className="complete-box-wrap">
<div className="complete-tit">{getMessage('join.complete.title')}</div>
<div className="complete-txt">{getMessage('join.complete.contents')}</div>
<div className="complete-email-wrap">
<div className="email-info">
{getMessage('join.complete.email_comment')} <span>{emailText}</span>
</div>
</div>
<div className="complete-btn">
<button
tyep="button"
className="btn-origin navy"
onClick={() => {
router.push('/login')
}}
>
{getMessage('join.btn.login_page')}
</button>
</div>
</div>
</div>
</div>
</div>
</>
)
}

View File

@ -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 = (
<div className="flex min-h-full flex-1 flex-col justify-center px-6 py-12 lg:px-8">
<form action={initPasswordProcess} className="space-y-6">
<h2 className="text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">{getMessage('login.init_password.title')}</h2>
<h2 className="text-center text-1xl font-bold leading-9 tracking-tight text-gray-900">{getMessage('login.init_password.sub_title')}</h2>
<div>
<label htmlFor="checkId" className="block text-sm font-medium leading-6 text-gray-900">
ID
</label>
<div className="mt-2">
<input
id="checkId"
name="checkId"
type="text"
required
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
</div>
</div>
<div>
<div className="flex items-center justify-between">
<label htmlFor="checkEmail" className="block text-sm font-medium leading-6 text-gray-900">
E-Mail
</label>
</div>
<div className="mt-2">
<input
id="checkEmail"
name="checkEmail"
type="email"
required
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
</div>
</div>
<p className="mt-5 text-center text-sm text-gray-500">
<Button type="submit" className="font-semibold leading-6 text-indigo-600 hover:text-indigo-500">
{getMessage('login.init_password.btn')}
</Button>
</p>
</form>
</div>
)
return (
<div className="flex flex-col align-center">
<div className="flex min-h-full flex-1 flex-col justify-center px-6 py-12 lg:px-8">
<div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
<h1 className="text-center text-4xl font-bold leading-9 tracking-tight text-gray-900">{getMessage('site.name')}</h1>
<h2 className="mt-5 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">{getMessage('site.sub_name')}</h2>
</div>
<div className="login-wrap">
<div className="login-inner">
<Link href={'/login'} className="login-logo">
<Image src="/static/images/main/login-logo.svg" alt="react" width={236} height={43} styles={{ width: '236px', height: '43px' }} priority />
</Link>
<div className="mt-5 sm:mx-auto sm:w-full sm:max-w-sm">
<form action={loginProcess} className="space-y-6">
<div>
<label htmlFor="userId" className="block text-sm font-medium leading-6 text-gray-900">
ID
</label>
<div className="mt-2">
<input
id="userId"
name="id"
type="text"
required
// autoComplete="email"
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
{passwordReset === 1 && (
<>
<div className="login-input-frame">
<form onSubmit={loginProcess} className="space-y-6">
<div className="login-frame-tit">
<span>{getMessage('site.name')}</span>
{getMessage('site.sub_name')}
</div>
<div className="login-input-wrap">
<div className="login-area id">
<input
type="text"
className="login-input"
id="userId"
name="id"
required
value={userId}
placeholder={getMessage('login.id.placeholder')}
onChange={(e) => {
setUserId(e.target.value)
}}
/>
<button
type="button"
className="id-delete"
onClick={(e) => {
setUserId('')
}}
></button>
</div>
<div className="login-area password">
<input
type={passwordVisible ? 'text' : 'password'}
className="login-input"
id="password"
name="password"
required
autoComplete="current-password"
placeholder={getMessage('login.password.placeholder')}
onChange={(e) => {
setPasswordVisible(passwordVisible)
}}
/>
<button
className={`password-hidden ${passwordVisible ? 'visible' : ''}`}
onClick={(e) => {
e.preventDefault()
setPasswordVisible(!passwordVisible)
}}
></button>
</div>
<div className="d-check-box login">
<input
type="checkbox"
id="ch01"
name="chkLoginId"
checked={chkLoginId}
onChange={(e) => {
setChkLoginId(e.target.checked)
}}
/>
<label htmlFor="ch01">{getMessage('login.id.save')}</label>
</div>
<div className="login-btn-box">
<button type="submit" className="login-btn">
{getMessage('login')}
</button>
</div>
<div className="reset-password">
<button type="button" onClick={() => setPasswordReset(2)}>
{getMessage('login.init_password.btn')}
</button>
</div>
</div>
</form>
</div>
<div className="login-guide-wrap">
<span></span>
{getMessage('login.guide.text')}
<br />
{getMessage('login.guide.sub1')} <Link href={'../join'}>{getMessage('login.guide.join.btn')}</Link>
{getMessage('login.guide.sub2')}
</div>
</>
)}
{passwordReset === 2 && (
<>
<div className="login-input-frame">
<div className="login-frame-tit pw-reset">
<span>{getMessage('login.init_password.title')}</span>
{getMessage('login.init_password.sub_title')}
</div>
<div className="login-input-wrap">
<div className="login-area id">
<input
type="text"
id="checkId"
name="checkId"
value={checkId}
required
className="login-input"
placeholder={getMessage('login.init_password.id.placeholder')}
onChange={(e) => {
setCheckId(e.target.value)
}}
/>
<button
type="button"
className="id-delete"
onClick={() => {
setCheckId('')
}}
></button>
</div>
<div className="login-area email">
<input
id="checkEmail"
name="checkEmail"
type="email"
required
className="login-input"
value={checkEmail}
onChange={(e) => {
setCheckEmail(e.target.value)
}}
placeholder={getMessage('login.init_password.email.placeholder')}
/>
<button
type="button"
className="id-delete"
onClick={() => {
setCheckEmail('')
}}
></button>
</div>
<div className="pwreset-btn-box">
<button type="button" className="login-btn light mr5" onClick={() => setPasswordReset(1)}>
{getMessage('login.init_password.btn.back')}
</button>
<button type="button" className="login-btn" onClick={initPasswordProcess}>
{getMessage('login.init_password.btn')}
</button>
</div>
</div>
</div>
<div>
<div className="flex items-center justify-between">
<label htmlFor="password" className="block text-sm font-medium leading-6 text-gray-900">
Password
</label>
</div>
<div className="mt-2">
<input
id="password"
name="password"
type="password"
required
autoComplete="current-password"
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
</div>
</div>
<div>
<button
type="submit"
className="mt-10 flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
{getMessage('login')}
</button>
</div>
</form>
<p className="mt-5 text-center text-sm text-gray-500">
<Button
onClick={() => {
setContent(initPasswordContent)
setOpen(true)
}}
className="font-semibold leading-6 text-indigo-600 hover:text-indigo-500"
>
{getMessage('login.init_password.btn')}
</Button>
</p>
<div className="flex align-center mt-2">
<Switch isSelected={isSelected} onValueChange={handleSelected}>
{isSelected ? 'Current Locale: KO' : 'Current Locale: JA'}
</Switch>
</div>
</div>
</>
)}
</div>
<div className="login-copyright">COPYRIGHT©2024 Hanwha Japan All Rights Reserved.</div>
</div>
)
}

View File

@ -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 = (
<div className="flex min-h-full flex-1 flex-col justify-center px-6 py-12 lg:px-8">
<form action={initPasswordProcess} className="space-y-6">
<h2 className="text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">{getMessage('login.init_password.title')}</h2>
<h2 className="text-center text-1xl font-bold leading-9 tracking-tight text-gray-900">{getMessage('login.init_password.sub_title')}</h2>
<div>
<label htmlFor="checkId" className="block text-sm font-medium leading-6 text-gray-900">
ID
</label>
<div className="mt-2">
<input
id="checkId"
name="checkId"
type="text"
required
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
</div>
</div>
<div>
<div className="flex items-center justify-between">
<label htmlFor="checkEmail" className="block text-sm font-medium leading-6 text-gray-900">
E-Mail
</label>
</div>
<div className="mt-2">
<input
id="checkEmail"
name="checkEmail"
type="email"
required
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
</div>
</div>
<p className="mt-5 text-center text-sm text-gray-500">
<button type="submit" className="font-semibold leading-6 text-indigo-600 hover:text-indigo-500">
{getMessage('login.init_password.btn')}
</button>
</p>
</form>
</div>
)
return (
<div className="login-wrap">
<div className="login-inner">
<Link href={'/login'} className="login-logo">
<Image src="/static/images/main/login-logo.svg" alt="react" width={236} height={43} styles={{ width: '236px', height: '43px' }} priority />
</Link>
<form action={loginProcess} className="space-y-6">
<div className="login-input-frame">
<div className="login-frame-tit">
<span>Q.CAST III</span>
太陽光発電システム図面管理サイト
</div>
<div className="login-input-wrap">
<div className="login-area id">
<input type="text" className="login-input" id="userId" name="id" required placeholder={'IDを入力してください'} />
<button className="id-delete" onClick={(e) => e.preventDefault()}></button>
</div>
<div className="login-area password">
<input
type="password"
className="login-input"
id="password"
name="password"
required
autoComplete="current-password"
ref={passwordRef}
/>
<button
className={`password-hidden ${passwordVisible ? 'visible' : ''}`}
onClick={(e) => {
e.preventDefault()
setPasswordVisible(!passwordVisible)
}}
></button>
</div>
<div className="d-check-box login">
<input type="checkbox" id="ch01" />
<label htmlFor="ch01">ID Save</label>
</div>
<div className="login-btn-box">
<button type="submit" className="login-btn">
Login
</button>
</div>
<div className="reset-password">
<Link href={'#'}>パスワードの初期化</Link>
</div>
</div>
</div>
</form>
<div className="login-guide-wrap">
<span></span>当サイトをご利用の際は事前申請が必要です
<br />
IDがない方は <Link href={'#'}>ID申請 クリックしてください</Link>
</div>
</div>
<div className="login-copyright">COPYRIGHT©2024 Hanwha Japan All Rights Reserved.</div>
</div>
)
}

View File

@ -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) //
}

View File

@ -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 (
<ol className="pagination">
<li className="page-item first">
<button onClick={() => handlePage(1)}></button>
</li>
<li className="page-item prev">
<button
onClick={() => {
if (currentPage === 1) return
handlePage(Math.max(1, (pageGroup - 2) * pagePerBlock + 1))
}}
></button>
</li>
{pageRange.map((page) => (
<li className={page === currentPage ? `page-item on` : `page-item`} key={page}>
<button onClick={() => handlePage(page)}>{page}</button>
</li>
))}
<li className="page-item next">
<button
onClick={() => {
if (currentPage === totalPages) return
handlePage(Math.max(1, pageGroup * pagePerBlock + 1))
}}
></button>
</li>
<li className="page-item last">
<button onClick={() => handlePage(totalPages)}></button>
</li>
</ol>
)
}

View File

@ -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 (
<>
<h1>Community Archive</h1>
<div className="sub-header">
<div className="sub-header-inner">
<ul className="sub-header-title-wrap">
<li className="title-item">
<Link className="sub-header-title" href={'#'}>
{getMessage('board.archive.title')}
</Link>
</li>
</ul>
<ul className="sub-header-location">
<li className="location-item">
<span className="home">
<Image src="/static/images/main/home_icon.svg" alt="react" width={16} height={16} />
</span>
</li>
<li className="location-item">
<span>{getMessage('header.menus.community')}</span>
</li>
<li className="location-item">
<span>{getMessage('board.archive.title')}</span>
</li>
</ul>
</div>
</div>
<div className="sub-content">
<div className="sub-content-inner">
<div className="sub-table-box">
<Search title={boardType.boardTitle} subTitle={boardType.subTitle} isSelectUse={true} />
<ArchiveTable clsCode={boardType.clsCode} />
</div>
</div>
</div>
</>
)
}

View File

@ -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 (
<>
<div className="file-down-list">
{boardList?.map((board) => (
<div key={board.noticeNo} className="file-down-item">
<div className="file-item-info">
<div className="item-num">
{/* 번호 */}
{board.rowNumber}
</div>
<div className="item-name">
{/* 제목 */}
{board.title}
</div>
<div className="item-date">
{/* 등록일 */}
{getMessage('board.sub.updDt')} : {board.uptDt ? board.uptDt : board.regDt}
</div>
</div>
<div className="file-down-box">
{/* 첨부파일 */}
<button type="button" className="file-down-btn" onClick={() => handleDetailFileListDown(board.noticeNo)}></button>
</div>
</div>
))}
</div>
</>
)
}

View File

@ -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 (
<>
<h1>Community FAQ</h1>
<div className="sub-header">
<div className="sub-header-inner">
<ul className="sub-header-title-wrap">
<li className="title-item">
<Link className="sub-header-title" href={'#'}>
{getMessage('board.faq.title')}
</Link>
</li>
</ul>
<ul className="sub-header-location">
<li className="location-item">
<span className="home">
<Image src="/static/images/main/home_icon.svg" alt="react" width={16} height={16} />
</span>
</li>
<li className="location-item">
<span>{getMessage('header.menus.community')}</span>
</li>
<li className="location-item">
<span>{getMessage('board.faq.title')}</span>
</li>
</ul>
</div>
</div>
<div className="sub-content">
<div className="sub-content-inner">
<div className="sub-table-box">
<Search title={boardType.boardTitle} subTitle={boardType.subTitle} isSelectUse={true} />
<Table clsCode={boardType.clsCode} />
<Pagination />
</div>
</div>
</div>
</>
)
}

View File

@ -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 (
<>
<h1>Community Notice</h1>
<div className="sub-header">
<div className="sub-header-inner">
<ul className="sub-header-title-wrap">
<li className="title-item">
<Link className="sub-header-title" href={'#'}>
{getMessage('board.notice.title')}
</Link>
</li>
</ul>
<ul className="sub-header-location">
<li className="location-item">
<span className="home">
<Image src="/static/images/main/home_icon.svg" alt="react" width={16} height={16} />
</span>
</li>
<li className="location-item">
<span>{getMessage('header.menus.community')}</span>
</li>
<li className="location-item">
<span>{getMessage('board.notice.title')}</span>
</li>
</ul>
</div>
</div>
<div className="sub-content">
<div className="sub-content-inner">
<div className="sub-table-box">
<Search title={boardType.boardTitle} subTitle={boardType.subTitle} isSelectUse={true} />
<Table clsCode={boardType.clsCode} />
<Pagination />
</div>
</div>
</div>
</>
)
}

View File

@ -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 (
<>
<ol className="pagination">
<li className="page-item first">
<button
type="button"
disabled={search.currentPage === 1}
onClick={() => {
handlePagination(1)
}}
></button>
</li>
<li className="page-item prev">
<button
type="button"
disabled={search.currentPage <= 1}
onClick={() => {
handlePagination(search.currentPage - 1)
}}
></button>
</li>
{/* 페이지목록 */}
{allPages.map((page, index) => {
return (
<li className={`page-item ${search.currentPage === page ? 'on' : ''}`} key={index}>
<button
type="button"
onClick={() => {
handlePagination(page)
}}
>
{page}
</button>
</li>
)
})}
<li className="page-item next">
<button
type="button"
disabled={search.currentPage >= totalPages}
onClick={() => {
handlePagination(search.currentPage + 1)
}}
></button>
</li>
<li className="page-item last">
<button
type="button"
disabled={search.currentPage === totalPages}
onClick={() => {
handlePagination(totalPages)
}}
></button>
</li>
</ol>
</>
)
}

View File

@ -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 (
<>
<div className="community-search-warp">
<div className="community-search-box">
<input
type="text"
className="community-input"
placeholder={getMessage('board.sub.search.placeholder')}
onChange={(e) => {
setSearchValue(e.target.value)
}}
onKeyDown={handleKeyDown}
value={searchValue}
/>
<button type="button" className="community-search-ico" onClick={() => handleSearch(searchValue, selectPageBlock)}></button>
</div>
{searchView && (
<div className="community-search-keyword">
{isSelectUse ? (
<>
<div
dangerouslySetInnerHTML={{
__html: getMessage('board.sub.search.result', [`<span>${searchViewText}</span>`, `<span>${search.totalCount}</span>`]),
}}
></div>
</>
) : (
<>
<div
dangerouslySetInnerHTML={{
__html: getMessage('board.sub.search.archive.result', [`<span>${searchViewText}</span>`, `<span>${search.totalCount}</span>`]),
}}
></div>
</>
)}
</div>
)}
</div>
<div className="table-box-title-wrap">
<div className="title-wrap">
<h3>{subTitle}</h3>
<ul className="info-wrap">
<li>
{getMessage('board.sub.total')} <span className="red">{search.totalCount}</span>
</li>
</ul>
</div>
{isSelectUse && (
<div className="left-unit-box">
<div className="select-box" style={{ width: '80px' }}>
<select
className="select-light black"
value={selectPageBlock}
onChange={(e) => {
setSelectPageBlock(Number(e.target.value))
const text = searchValue ? searchValue : searchViewText
handleSearch(text, Number(e.target.value))
}}
>
<option value={100}>100</option>
<option value={200}>200</option>
<option value={300}>300</option>
<option value={400}>400</option>
<option value={500}>500</option>
</select>
</div>
</div>
)}
</div>
</>
)
}

View File

@ -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 (
<>
<div className="community-table">
<table>
<colgroup>
<col width={100} />
<col />
<col width={200} />
</colgroup>
<tbody>
{boardList.length > 0 ? (
boardList?.map((board) => (
<tr
key={board.noticeNo}
onClick={() => {
setOpen(true)
setModalNoticeNo(board.noticeNo)
}}
>
<td className="al-c">
{/* 번호 */}
{board.rowNumber}
</td>
<td style={{ textAlign: 'center' }}>
{/* 제목 */}
<div className="text-frame">
<div className="text-overflow">{board.title}</div>
{board.attachYn && <span className="clip"></span>}
</div>
</td>
<td className="al-c">
{/* 등록일 */}
{board.regDt.split(' ')[0]}
</td>
</tr>
))
) : (
<tr>
<td colSpan={3} className="al-c">
{getMessage('common.message.no.data')}
</td>
</tr>
)}
</tbody>
</table>
</div>
{open && <BoardDetailModal noticeNo={modalNoticeNo} setOpen={setOpen} />}
</>
)
}

View File

@ -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 (
<>
<div key={noticeNo} className="modal-popup community">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<button
type="button"
className="modal-close"
onClick={() => {
setOpen(false)
}}
>
닫기
</button>
</div>
<div className="modal-body">
<div className="community_detail">
<div className="community_detail-tit">{boardDetail.title}</div>
{boardDetail.listFile && (
<dl className="community_detail-file-wrap">
<dt>첨부파일 목록</dt>
{boardDetail.listFile.map((boardFile) => (
<dd key={boardFile.encodeFileNo}>
<button type="button" className="down" onClick={() => handleFileDown(boardFile)}>
{boardFile.srcFileNm}
</button>
</dd>
))}
</dl>
)}
<div className="community_detail-inner">{boardDetail.contents}</div>
</div>
</div>
</div>
</div>
</div>
</>
)
}

View File

@ -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) {

View File

@ -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,

View File

@ -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 (
<div className="canvas-frame flex justify-center">
@ -162,11 +40,12 @@ export default function CanvasFrame({ plan }) {
{contextMenu.map((menus, index) => (
<ul key={index}>
{menus.map((menu) => (
<li>{menu.name}</li>
<li onClick={(e) => setCurrentContextMenu(menu)}>{menu.name}</li>
))}
</ul>
))}
</QContextMenu>
{currentContextMenu?.component}
</div>
)
}

View File

@ -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') + `</br>${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() {
<div className="canvas-page-list">
<div className="canvas-plane-wrap">
{plans.map((plan) => (
<button key={plan.id} className={`canvas-page-box ${plan.isCurrent === true ? 'on' : ''}`} onClick={() => handleCurrentPlan(plan.id)}>
<button
key={`plan-${plan.id}`}
className={`canvas-page-box ${plan.isCurrent === true ? 'on' : ''}`}
onClick={() => handleCurrentPlan(sessionState.userId, plan.id)}
>
<span>{plan.name}</span>
<i
className="close"
onClick={(e) =>
swalFire({
html: getMessage('common.message.confirm.delete') + `</br>${plan.name}`,
text: `${plan.name} ` + getMessage('plan.message.confirm.delete'),
type: 'confirm',
confirmFn: () => {
handleDeletePlan(e, plan.id)
@ -127,9 +52,11 @@ export default function CanvasLayout() {
</button>
))}
</div>
<button className="plane-add" onClick={addNewPlan}>
<span></span>
</button>
{plans.length < 10 && (
<button className="plane-add" onClick={() => handleAddPlan(sessionState.userId, objectNo)}>
<span></span>
</button>
)}
</div>
<CanvasFrame plan={plans.find((plan) => plan.isCurrent === true)} />
</div>

View File

@ -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') + `</br>${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) {
<ul className="canvas-menu-list">
{canvasMenus.map((menu) => {
return (
<li key={menu.index} className={`canvas-menu-item ${menuNumber === menu.index ? 'active' : ''}`} onClick={() => onClickNav(menu)}>
<li
key={`canvas-menu-${menu.index}`}
className={`canvas-menu-item ${menuNumber === menu.index ? 'active' : ''}`}
onClick={() => onClickNav(menu)}
>
<button>
<span className={`menu-icon ${menu.icon}`}></span>
{getMessage(menu.name)}
@ -193,7 +204,12 @@ export default function CanvasMenu(props) {
<button className="btn06"></button>
</div>
<div className="size-control">
<button className="control-btn minus"></button>
<button
className="control-btn minus"
onClick={() => {
canvas.setZoom(canvas.getZoom() - 0.1)
}}
></button>
<span>{canvasZoom}%</span>
<button className="control-btn plus" onClick={handleZoomClear}></button>
</div>

View File

@ -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 && <ObjectSetting setShowObjectSettingModal={setShowObjectSettingModal} />}
{showPlacementSurfaceSettingModal && <PlacementSurfaceSetting setShowPlacementSurfaceSettingModal={setShowPlacementSurfaceSettingModal} />}
{showBasicSettingModal && <BasicSetting setShowBasicSettingModal={setShowBasicSettingModal} />}
{showCircuitTrestleSettingModal && <CircuitTrestleSetting setShowCircuitTrestleSettingModal={setShowCircuitTrestleSettingModal} />}
</div>
</div>
</>

View File

@ -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)
}
}

View File

@ -49,7 +49,7 @@ export default function AuxiliaryDrawing({ setShowAuxiliaryModal }) {
handleFix,
buttonAct,
setButtonAct,
} = useAuxiliaryDrawing()
} = useAuxiliaryDrawing(setShowAuxiliaryModal)
const outerLineProps = {
length1,

View File

@ -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 (
<WithDraggable isShow={true} pos={{ x: 0, y: 150 }}>
<div className={`modal-pop-wrap xm`}>
<div className="modal-head">
<h1 className="title">補助線の移動 </h1>
<button className="modal-close" onClick={() => setCurrentContextMenu(null)}>
닫기
</button>
</div>
<div className="modal-body">
<div className="grid-option-tit">移動する方向を入力してください</div>
<div className="grid-option-wrap">
<div className="grid-option-box">
<div className="move-form">
<p className="mb5">長さ</p>
<div className="input-move-wrap mb5">
<div className="input-move">
<input type="text" className="input-origin" defaultValue={910} />
</div>
<span>mm</span>
</div>
<div className="input-move-wrap">
<div className="input-move">
<input type="text" className="input-origin" defaultValue={910} />
</div>
<span>mm</span>
</div>
</div>
<div className="direction-move-wrap">
<button className="direction up"></button>
<button className="direction down act"></button>
<button className="direction left"></button>
<button className="direction right"></button>
</div>
</div>
</div>
<div className="grid-btn-wrap">
<button className="btn-frame modal act">保存</button>
</div>
</div>
</div>
</WithDraggable>
)
}

View File

@ -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 (
<WithDraggable isShow={true} pos={{ x: 0, y: 150 }}>
<div className={`modal-pop-wrap xm`}>
<div className="modal-head">
<h1 className="title">補助線サイズ変更 </h1>
<button className="modal-close" onClick={() => setCurrentContextMenu(null)}>
닫기
</button>
</div>
<div className="modal-body">
<div className="discrimination-box mb10">
<div className="d-check-radio pop mb10">
<input type="radio" name="radio01" id="ra01" />
<label htmlFor="ra01">1支店</label>
</div>
<div className="outline-form mb15">
<div className="input-grid mr5" style={{ flex: '1 1 auto' }}>
<input type="text" className="input-origin block" defaultValue={100} />
</div>
<span className="thin">mm</span>
</div>
<div className="outline-form">
<span style={{ width: 'auto' }}>長さ</span>
<div className="input-grid mr5">
<input type="text" className="input-origin block" defaultValue={100} />
</div>
<span className="thin">mm</span>
</div>
</div>
<div className="discrimination-box ">
<div className="d-check-radio pop mb10">
<input type="radio" name="radio01" id="ra02" />
<label htmlFor="ra02">2支店</label>
</div>
<div className="outline-form mb15">
<div className="input-grid mr5" style={{ flex: '1 1 auto' }}>
<input type="text" className="input-origin block" defaultValue={100} />
</div>
<span className="thin">mm</span>
</div>
<div className="outline-form">
<span style={{ width: 'auto' }}>長さ</span>
<div className="input-grid mr5">
<input type="text" className="input-origin block" defaultValue={100} />
</div>
<span className="thin">mm</span>
</div>
</div>
<div className="grid-btn-wrap">
<button className="btn-frame modal act">保存</button>
</div>
</div>
</div>
</WithDraggable>
)
}

View File

@ -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 (
<WithDraggable isShow={true} pos={{ x: 50, y: -950 }}>
<div className={`modal-pop-wrap lx-2`}>
@ -27,8 +31,13 @@ export default function BasicSetting({ setShowBasicSettingModal }) {
<div className={`module-tab-bx ${tabNum === 3 ? 'act' : ''}`}>{getMessage('modal.module.basic.setting.module.placement')}</div>
</div>
{tabNum === 1 && <Orientation setTabNum={setTabNum} />}
{tabNum === 2 && <Module setTabNum={setTabNum} />}
{tabNum === 3 && <Placement setTabNum={setTabNum} />}
{/*배치면 초기설정 - 입력방법: 복시도 입력 || 실측값 입력*/}
{canvasSetting.roofSizeSet && canvasSetting.roofSizeSet != 3 && tabNum === 2 && <Module setTabNum={setTabNum} />}
{canvasSetting.roofSizeSet && canvasSetting.roofSizeSet != 3 && tabNum === 3 && <Placement setTabNum={setTabNum} />}
{/*배치면 초기설정 - 입력방법: 육지붕*/}
{canvasSetting.roofSizeSet && canvasSetting.roofSizeSet == 3 && tabNum === 2 && <PitchModule setTabNum={setTabNum} />}
{canvasSetting.roofSizeSet && canvasSetting.roofSizeSet == 3 && tabNum === 3 && <PitchPlacement setTabNum={setTabNum} />}
<div className="grid-btn-wrap">
{tabNum !== 1 && (
@ -36,7 +45,7 @@ export default function BasicSetting({ setShowBasicSettingModal }) {
{getMessage('modal.module.basic.setting.prev')}
</button>
)}
{tabNum !== 3 && <button className="btn-frame modal act mr5">{getMessage('modal.common.save')}</button>}
{/*{tabNum !== 3 && <button className="btn-frame modal act mr5">{getMessage('modal.common.save')}</button>}*/}
{tabNum !== 3 && (
<button className="btn-frame modal" onClick={() => setTabNum(tabNum + 1)}>
Next

View File

@ -74,12 +74,15 @@ export default function Module({}) {
</tr>
</>
))}
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
{Array.from({ length: 3 - moduleData.rows.length }).map((_, i) => (
<tr key={i}>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
))}
</tbody>
</table>
</div>

View File

@ -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 (
<>
<div className="module-table-box">
<div className="module-table-inner">
<div className="outline-form mb10">
<span className="mr10">{getMessage('modal.module.basic.setting.module.setting')}</span>
<div className="grid-select">
<QSelectBox title={'Search'} option={SelectOption01} />
</div>
</div>
<div className="roof-module-table">
<table>
<thead>
<tr>
{moduleData.header.map((data) => (
<th key={data.prop} style={{ width: data.width ? data.width : '' }}>
{data.name}
</th>
))}
</tr>
</thead>
<tbody>
{moduleData.rows.map((row) => (
<>
<tr>
{moduleData.header.map((header) => (
<>
{header.type === 'color-box' && (
<td>
<div className="color-wrap">
<span className="color-box" style={{ backgroundColor: row[header.prop].color }}></span>
<span className="name">{row[header.prop].name}</span>
</div>
</td>
)}
{!header.type && header.type !== 'color-box' && <td className="al-r">{row[header.prop].name}</td>}
</>
))}
</tr>
</>
))}
{Array.from({ length: 3 - moduleData.rows.length }).map((_, i) => (
<tr key={i}>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</>
)
}

View File

@ -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 (
<>
<div className="module-table-box mb10">
<div className="module-table-inner">
<div className="roof-module-table">
<table>
<thead>
<tr>
{moduleData.header.map((data) => (
<th key={data.prop} style={{ width: data.width ? data.width : '' }}>
{data.type === 'check' ? (
<div className="d-check-box no-text pop">
<input type="checkbox" id="ch01" disabled />
<label htmlFor="ch01"></label>
</div>
) : (
data.name
)}
</th>
))}
</tr>
</thead>
<tbody>
{moduleData.rows.map((row) => (
<>
<tr>
{moduleData.header.map((header) => (
<>
{header.type === 'color-box' && (
<td>
<div className="color-wrap">
<span className="color-box" style={{ backgroundColor: row[header.prop].color }}></span>
<span className="name">{row[header.prop].name}</span>
</div>
</td>
)}
{header.type === 'check' && (
<td className="al-c">
<div className="d-check-box no-text pop">
<input type="checkbox" id="ch02" />
<label htmlFor="ch02"></label>
</div>
</td>
)}
{header.type && header.type !== 'color-box' && header.type !== 'check' && <td className="al-r">{row[header.prop].name}</td>}
</>
))}
</tr>
</>
))}
</tbody>
</table>
</div>
</div>
</div>
<div className="module-table-box mb10">
<div className="module-table-inner">
<div className="hexagonal-wrap">
<div className="hexagonal-item">
<div className="bold-font">{getMessage('modal.module.basic.setting.pitch.module.placement.standard.setting')}</div>
</div>
<div className="hexagonal-item">
<div className="pop-form-radio">
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra01" />
<label htmlFor="ra01">{getMessage('modal.module.basic.setting.pitch.module.placement.standard.setting.south')}</label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra02" />
<label htmlFor="ra02">{getMessage('modal.module.basic.setting.pitch.module.placement.standard.setting.select')}</label>
</div>
</div>
</div>
</div>
</div>
</div>
</>
)
}

View File

@ -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 (
<WithDraggable isShow={true} pos={{ x: 50, y: -950 }}>
<div className={`modal-pop-wrap lx-2`}>
<div className="modal-head">
<h1 className="title">{getMessage('modal.circuit.trestle.setting')} </h1>
<button className="modal-close" onClick={() => setShowCircuitTrestleSettingModal(false)}>
닫기
</button>
</div>
<div className="modal-body">
<div className="roof-module-tab">
<div className={`module-tab-bx ${tabNum === 1 || tabNum === 2 || tabNum === 3 ? 'act' : ''}`}>
{getMessage('modal.circuit.trestle.setting.power.conditional.select')}
</div>
<span className={`tab-arr ${tabNum === 2 || tabNum === 3 ? 'act' : ''}`}></span>
<div className={`module-tab-bx ${tabNum === 2 || tabNum === 3 ? 'act' : ''}`}>
{getMessage('modal.circuit.trestle.setting.circuit.allocation')}
</div>
<span className={`tab-arr ${tabNum === 3 ? 'act' : ''}`}></span>
<div className={`module-tab-bx ${tabNum === 3 ? 'act' : ''}`}>{getMessage('modal.circuit.trestle.setting.step.up.allocation')}</div>
</div>
{tabNum === 1 && <PowerConditionalSelect />}
{tabNum === 2 && <CircuitAllocation {...circuitProps} />}
{tabNum === 3 && <StepUp />}
<div className="grid-btn-wrap">
{tabNum !== 1 && (
<button className="btn-frame modal mr5" onClick={() => setTabNum(tabNum - 1)}>
{getMessage('modal.common.prev')}
</button>
)}
{tabNum !== 3 && (
<button className="btn-frame modal act" onClick={() => setTabNum(tabNum + 1)}>
Next
</button>
)}
{tabNum === 3 && <button className="btn-frame modal act">保存 (仮割り当て)</button>}
</div>
</div>
</div>
</WithDraggable>
)
}

View File

@ -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 (
<>
<div className="module-box-tab">
<button className={`module-btn ${circuitAllocationType === 1 ? 'act' : ''}`} onClick={() => setCircuitAllocationType(1)}>
{getMessage('modal.circuit.trestle.setting.circuit.allocation.auto')}
</button>
<button className={`module-btn ${circuitAllocationType === 2 ? 'act' : ''}`} onClick={() => setCircuitAllocationType(2)}>
{getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity')}
</button>
</div>
<div className="properties-setting-wrap outer">
<div className="setting-tit">{getMessage('modal.circuit.trestle.setting.circuit.allocation')}</div>
{circuitAllocationType === 1 && <AutoCircuitAllocation />}
{circuitAllocationType === 2 && <PassivityCircuitAllocation />}
</div>
</>
)
}

View File

@ -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 (
<>
<div className="outline-form mb15">
<span className="mr10">分類 (余剰)</span>
<div className="grid-select mr10">
<QSelectBox title={'HQJPシリーズ'} option={SelectOption01} />
</div>
<span className="thin">寒冷地仕様</span>
</div>
<div className="module-table-box mb10">
<div className="module-table-inner">
<div className="circuit-check-inner">
<div className="d-check-box pop mb15 sel">
<input type="checkbox" id="ch01" />
<label htmlFor="ch01">屋内PCSHQJP-KA-5シリーズ</label>
</div>
<div className="d-check-box pop sel">
<input type="checkbox" id="ch02" />
<label htmlFor="ch02">屋外マルチPCSHQJP-RA5シリーズ</label>
</div>
</div>
</div>
</div>
<div className="module-table-box mb15">
<div className="module-table-inner">
<div className="x-scroll-table mb10">
<div className="roof-module-table">
<table>
<thead>
<tr>
{seriesData.header.map((header) => (
<th key={header.prop} style={{ width: header.width }}>
{header.name}
</th>
))}
</tr>
</thead>
<tbody>
{seriesData.rows.map((row, index) => (
<tr key={index} onClick={() => setSelectedRowIndex(index)} className={index === selectedRowIndex ? 'on' : ''}>
{seriesData.header.map((header) => (
<td>{row[header.prop].name}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</div>
<div className="circuit-right-wrap mb10">
<button className="btn-frame self mr5">追加</button>
</div>
<div className="circuit-data-form">
<span className="normal-font">
HQJP-KA40-5 <button className="del"></button>
</span>
<span className="normal-font">
HQJP-KA40-5 <button className="del"></button>
</span>
<span className="normal-font">
HQJP-KA40-5 <button className="del"></button>
</span>
</div>
</div>
</div>
<div className="slope-wrap">
<div className="d-check-box pop mb15">
<input type="checkbox" id="ch03" />
<label htmlFor="ch03">同一傾斜同一方面の面積の場合同じ面として回路を分ける</label>
</div>
<div className="d-check-box pop">
<input type="checkbox" id="ch04" />
<label className="red" htmlFor="ch04">
MAX接続過積で回路を分ける
</label>
</div>
</div>
</>
)
}

View File

@ -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 (
<>
<div className="properties-setting-wrap outer">
<div className="setting-tit">{getMessage('modal.circuit.trestle.setting.step.up.allocation')}</div>
<div className="circuit-overflow">
<div className="module-table-box mb10">
<div className="module-table-inner">
<div className="mb-box">
<div className="circuit-table-tit">HQJP-KA55-5</div>
<div className="roof-module-table overflow-y">
<table>
<thead>
<tr>
<th>シリアル枚数</th>
<th>総回路数</th>
</tr>
</thead>
<tbody>
<tr>
<td className="al-r">10</td>
<td className="al-r">0</td>
</tr>
<tr>
<td className="al-r">10</td>
<td className="al-r">0</td>
</tr>
<tr>
<td className="al-r">10</td>
<td className="al-r">0</td>
</tr>
<tr>
<td className="al-r">10</td>
<td className="al-r">0</td>
</tr>
<tr>
<td className="al-r">10</td>
<td className="al-r">0</td>
</tr>
</tbody>
</table>
</div>
</div>
<div className="circuit-table-flx-wrap">
<div className="circuit-table-flx-box">
<div className="bold-font mb10">接続する</div>
<div className="roof-module-table mb10">
<table>
<thead>
<tr>
<th style={{ width: '140px' }}>名称</th>
<th>回路数</th>
<th>昇圧回路数</th>
</tr>
</thead>
<tbody>
<tr>
<td className="al-c">KTN-CBD4C</td>
<td className="al-r">4</td>
<td className="al-r">0</td>
</tr>
<tr>
<td className="al-c">KTN-CBD4C</td>
<td className="al-r">4</td>
<td className="al-r">0</td>
</tr>
<tr>
<td className="al-c">KTN-CBD4C</td>
<td className="al-r">4</td>
<td className="al-r">0</td>
</tr>
</tbody>
</table>
</div>
<div className="bottom-wrap">
<div className="circuit-right-wrap mb10">
<button className="btn-frame self mr5">追加</button>
</div>
<div className="circuit-data-form">
<span className="normal-font">
HQJP-KA40-5<button className="del"></button>
</span>
</div>
</div>
</div>
<div className="circuit-table-flx-box">
<div className="bold-font mb10">昇圧オプション</div>
<div className="roof-module-table mb10">
<table>
<thead>
<tr>
<th>名称</th>
<th>昇圧回路数</th>
</tr>
</thead>
<tbody>
<tr>
<td className="al-c">-</td>
<td className="al-c">-</td>
</tr>
<tr>
<td className="al-c">-</td>
<td className="al-c">-</td>
</tr>
</tbody>
</table>
</div>
<div className="bottom-wrap">
<div className="circuit-right-wrap mb10">
<button className="btn-frame self mr5">追加</button>
</div>
<div className="circuit-data-form">
<span className="normal-font">
HQJP-KA40-5<button className="del"></button>
</span>
</div>
</div>
</div>
</div>
<div className="circuit-count-input">
<span className="normal-font">綿調道区分</span>
<div className="input-grid mr5" style={{ width: '40px' }}>
<input type="text" className="input-origin block" />
</div>
<span className="normal-font">回路</span>
<span className="normal-font">(二重昇圧回路数</span>
<div className="input-grid mr5" style={{ width: '40px' }}>
<input type="text" className="input-origin block" />
</div>
<span className="normal-font">回路)</span>
</div>
</div>
</div>
<div className="module-table-box mb10">
<div className="module-table-inner">
<div className="mb-box">
<div className="circuit-table-tit">HQJP-KA55-5</div>
<div className="roof-module-table overflow-y">
<table>
<thead>
<tr>
<th>シリアル枚数</th>
<th>総回路数</th>
</tr>
</thead>
<tbody>
<tr>
<td className="al-r">10</td>
<td className="al-r">0</td>
</tr>
<tr>
<td className="al-r">10</td>
<td className="al-r">0</td>
</tr>
<tr>
<td className="al-r">10</td>
<td className="al-r">0</td>
</tr>
<tr>
<td className="al-r">10</td>
<td className="al-r">0</td>
</tr>
<tr>
<td className="al-r">10</td>
<td className="al-r">0</td>
</tr>
</tbody>
</table>
</div>
</div>
<div className="circuit-table-flx-wrap">
<div className="circuit-table-flx-box">
<div className="bold-font mb10">接続する</div>
<div className="roof-module-table mb10">
<table>
<thead>
<tr>
<th style={{ width: '140px' }}>名称</th>
<th>回路数</th>
<th>昇圧回路数</th>
</tr>
</thead>
<tbody>
<tr>
<td className="al-c">KTN-CBD4C</td>
<td className="al-r">4</td>
<td className="al-r">0</td>
</tr>
<tr>
<td className="al-c">KTN-CBD4C</td>
<td className="al-r">4</td>
<td className="al-r">0</td>
</tr>
<tr>
<td className="al-c">KTN-CBD4C</td>
<td className="al-r">4</td>
<td className="al-r">0</td>
</tr>
</tbody>
</table>
</div>
<div className="bottom-wrap">
<div className="circuit-right-wrap mb10">
<button className="btn-frame self mr5">追加</button>
</div>
<div className="circuit-data-form">
<span className="normal-font">
HQJP-KA40-5<button className="del"></button>
</span>
</div>
</div>
</div>
<div className="circuit-table-flx-box">
<div className="bold-font mb10">昇圧オプション</div>
<div className="roof-module-table mb10">
<table>
<thead>
<tr>
<th>名称</th>
<th>昇圧回路数</th>
</tr>
</thead>
<tbody>
<tr>
<td className="al-c">-</td>
<td className="al-c">-</td>
</tr>
<tr>
<td className="al-c">-</td>
<td className="al-c">-</td>
</tr>
</tbody>
</table>
</div>
<div className="bottom-wrap">
<div className="circuit-right-wrap mb10">
<button className="btn-frame self mr5">追加</button>
</div>
<div className="circuit-data-form">
<span className="normal-font">
HQJP-KA40-5<button className="del"></button>
</span>
</div>
</div>
</div>
</div>
<div className="circuit-count-input">
<span className="normal-font">綿調道区分</span>
<div className="input-grid mr5" style={{ width: '40px' }}>
<input type="text" className="input-origin block" />
</div>
<span className="normal-font">回路</span>
<span className="normal-font">(二重昇圧回路数</span>
<div className="input-grid mr5" style={{ width: '40px' }}>
<input type="text" className="input-origin block" />
</div>
<span className="normal-font">回路)</span>
</div>
</div>
</div>
<div className="module-table-box ">
<div className="module-table-inner">
<div className="mb-box">
<div className="circuit-table-tit">HQJP-KA55-5</div>
<div className="roof-module-table overflow-y">
<table>
<thead>
<tr>
<th>シリアル枚数</th>
<th>総回路数</th>
</tr>
</thead>
<tbody>
<tr>
<td className="al-r">10</td>
<td className="al-r">0</td>
</tr>
<tr>
<td className="al-r">10</td>
<td className="al-r">0</td>
</tr>
<tr>
<td className="al-r">10</td>
<td className="al-r">0</td>
</tr>
<tr>
<td className="al-r">10</td>
<td className="al-r">0</td>
</tr>
<tr>
<td className="al-r">10</td>
<td className="al-r">0</td>
</tr>
</tbody>
</table>
</div>
</div>
<div className="circuit-table-flx-wrap">
<div className="circuit-table-flx-box">
<div className="bold-font mb10">接続する</div>
<div className="roof-module-table mb10">
<table>
<thead>
<tr>
<th style={{ width: '140px' }}>名称</th>
<th>回路数</th>
<th>昇圧回路数</th>
</tr>
</thead>
<tbody>
<tr>
<td className="al-c">KTN-CBD4C</td>
<td className="al-r">4</td>
<td className="al-r">0</td>
</tr>
<tr>
<td className="al-c">KTN-CBD4C</td>
<td className="al-r">4</td>
<td className="al-r">0</td>
</tr>
<tr>
<td className="al-c">KTN-CBD4C</td>
<td className="al-r">4</td>
<td className="al-r">0</td>
</tr>
</tbody>
</table>
</div>
<div className="bottom-wrap">
<div className="circuit-right-wrap mb10">
<button className="btn-frame self mr5">追加</button>
</div>
<div className="circuit-data-form">
<span className="normal-font">
HQJP-KA40-5<button className="del"></button>
</span>
</div>
</div>
</div>
<div className="circuit-table-flx-box">
<div className="bold-font mb10">昇圧オプション</div>
<div className="roof-module-table mb10">
<table>
<thead>
<tr>
<th>名称</th>
<th>昇圧回路数</th>
</tr>
</thead>
<tbody>
<tr>
<td className="al-c">-</td>
<td className="al-c">-</td>
</tr>
<tr>
<td className="al-c">-</td>
<td className="al-c">-</td>
</tr>
</tbody>
</table>
</div>
<div className="bottom-wrap">
<div className="circuit-right-wrap mb10">
<button className="btn-frame self mr5">追加</button>
</div>
<div className="circuit-data-form">
<span className="normal-font">
HQJP-KA40-5<button className="del"></button>
</span>
</div>
</div>
</div>
</div>
<div className="circuit-count-input">
<span className="normal-font">綿調道区分</span>
<div className="input-grid mr5" style={{ width: '40px' }}>
<input type="text" className="input-origin block" />
</div>
<span className="normal-font">回路</span>
<span className="normal-font">(二重昇圧回路数</span>
<div className="input-grid mr5" style={{ width: '40px' }}>
<input type="text" className="input-origin block" />
</div>
<span className="normal-font">回路)</span>
</div>
</div>
</div>
</div>
<div className="slope-wrap">
<div className="outline-form">
<span className="mr10" style={{ width: 'auto' }}>
モニターの選択
</span>
<div className="grid-select mr10">
<QSelectBox title={'電力検出ユニット (モニター付き)'} option={SelectOption01} />
</div>
</div>
</div>
</div>
{/*<div className="grid-btn-wrap">*/}
{/* <button className="btn-frame modal mr5" onClick={() => setTabNum(2)}>*/}
{/* 以前*/}
{/* </button>*/}
{/* <button className="btn-frame modal act">保存 (仮割り当て)</button>*/}
{/*</div>*/}
</>
)
}

View File

@ -0,0 +1,17 @@
import { useMessage } from '@/hooks/useMessage'
export default function AutoCircuitAllocation() {
const { getMessage } = useMessage()
return (
<div className="module-table-box">
<div className="module-table-inner">
<div className="circuit-check-inner">
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra01" checked={true} />
<label htmlFor="ra01">{getMessage('modal.circuit.trestle.setting.circuit.allocation.auto')}</label>
</div>
</div>
</div>
</div>
)
}

View File

@ -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 (
<>
<div className="module-table-box mb10">
<div className="module-table-inner">
<div className="bold-font mb10">{getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity')}</div>
<div className="normal-font mb15">{getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.info')}</div>
<div className="roof-module-table overflow-y">
<table>
<thead>
<tr>
{moduleData.header.map((header) => (
<th key={header.prop}>{header.name}</th>
))}
</tr>
</thead>
<tbody>
{moduleData.rows.map((row, index) => (
<tr key={index}>
{moduleData.header.map((header) => (
<td key={header.prop}>{row[header.prop].name}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
<div className="module-table-box mb10">
<div className="module-table-inner">
<div className="hexagonal-wrap">
<div className="hexagonal-item">
<div className="bold-font">{getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.selected.power.conditional')}</div>
</div>
<div className="hexagonal-item">
<div className="d-check-radio pop mb10">
<input type="radio" name="radio01" id="ra01" />
<label htmlFor="ra01">HQJP-KA55-5 (標準回路2枚10)</label>
</div>
<div className="d-check-radio pop mb10">
<input type="radio" name="radio01" id="ra02" />
<label htmlFor="ra02">HQJP-KA55-5 (標準回路2枚10)</label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra03" />
<label htmlFor="ra03">HQJP-KA55-5 (標準回路2枚10)</label>
</div>
</div>
</div>
</div>
</div>
<div className="slope-wrap">
<div className="circuit-right-wrap mb15">
<div className="outline-form">
<span className="mr10" style={{ width: 'auto' }}>
{getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.circuit.num')}
</span>
<div className="input-grid mr5" style={{ width: '70px' }}>
<input type="text" className="input-origin block" />
</div>
</div>
</div>
<div className="circuit-right-wrap">
<button className="btn-frame roof mr5">
{getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.selected.power.conditional.reset')}
</button>
<button className="btn-frame roof mr5">
{getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.all.power.conditional.reset')}
</button>
<button className="btn-frame roof">{getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.circuit.num.fix')}</button>
</div>
</div>
</>
)
}

View File

@ -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,

View File

@ -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 }) {
</div>
<div className="properties-setting-wrap outer">
<div className="setting-tit">{getMessage('setting')}</div>
{buttonAct === 1 && <OpenSpace />}
{buttonAct === 2 && <Shadow />}
{buttonAct === 3 && <TriangleDormer />}
{buttonAct === 4 && <PentagonDormer />}
{buttonAct === 1 && <OpenSpace ref={objectPlacement} />}
{buttonAct === 2 && <Shadow ref={objectPlacement} />}
{buttonAct === 3 && <TriangleDormer ref={dormerPlacement} />}
{buttonAct === 4 && <PentagonDormer ref={dormerPlacement} />}
</div>
<div className="grid-btn-wrap">
<button className="btn-frame modal act">{getMessage('write')}</button>
<button
className="btn-frame modal act"
onClick={() => {
applyObject()
}}
>
{getMessage('write')}
</button>
</div>
</div>
</div>

View File

@ -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 (
<div className="discrimination-box mb10">
<div className="mb-box">
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra01" />
<input
type="radio"
name="radio01"
id="ra01"
value={INPUT_TYPE.FREE}
defaultChecked
ref={(el) => (refs.typeRef.current[0] = el)}
onClick={() => setSelectedType(INPUT_TYPE.FREE)}
/>
<label htmlFor="ra01">{getMessage('modal.object.setting.free.input')}</label>
</div>
</div>
<div className="mb-box">
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra02" />
<input
type="radio"
name="radio01"
id="ra02"
value={INPUT_TYPE.DIMENSION}
ref={(el) => (refs.typeRef.current[1] = el)}
onClick={() => setSelectedType(INPUT_TYPE.DIMENSION)}
/>
<label htmlFor="ra02">{getMessage('modal.object.setting.size.input')}</label>
</div>
</div>
@ -24,7 +51,13 @@ export default function OpenSpace() {
<div className="eaves-keraba-td">
<div className="outline-form">
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" defaultValue={100} />
<input
type="text"
className="input-origin block"
placeholder={0}
ref={refs.widthRef}
disabled={selectedType !== INPUT_TYPE.DIMENSION}
/>
</div>
<span className="thin">mm</span>
</div>
@ -35,7 +68,13 @@ export default function OpenSpace() {
<div className="eaves-keraba-td">
<div className="outline-form">
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" defaultValue={100} />
<input
type="text"
className="input-origin block"
placeholder={0}
ref={refs.heightRef}
disabled={selectedType !== INPUT_TYPE.DIMENSION}
/>
</div>
<span className="thin">mm</span>
</div>
@ -45,9 +84,11 @@ export default function OpenSpace() {
</div>
</div>
<div className="d-check-box pop">
<input type="checkbox" id="ch99" />
<input type="checkbox" id="ch99" ref={refs.isCrossRef} />
<label htmlFor="ch99">{getMessage('modal.object.setting.area.cross')}</label>
</div>
</div>
)
}
})
export default OpenSpace

View File

@ -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 (
<div className="discrimination-box mb10">
<div className="mb-box">
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra01" />
<input
type="radio"
name="radio01"
id="ra01"
value={INPUT_TYPE.FREE}
defaultChecked
ref={(el) => (refs.typeRef.current[0] = el)}
onClick={() => setSelectedType(INPUT_TYPE.FREE)}
/>
<label htmlFor="ra01">{getMessage('modal.object.setting.free.input')}</label>
</div>
</div>
<div className="mb-box">
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra02" />
<input
type="radio"
name="radio01"
id="ra02"
value={INPUT_TYPE.DIMENSION}
ref={(el) => (refs.typeRef.current[1] = el)}
onClick={() => setSelectedType(INPUT_TYPE.DIMENSION)}
/>
<label htmlFor="ra02">{getMessage('modal.object.setting.size.input')}</label>
</div>
</div>
@ -24,7 +51,13 @@ export default function Shadow() {
<div className="eaves-keraba-td">
<div className="outline-form">
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" defaultValue={100} />
<input
type="text"
className="input-origin block"
placeholder={0}
ref={refs.widthRef}
disabled={selectedType !== INPUT_TYPE.DIMENSION}
/>
</div>
<span className="thin">mm</span>
</div>
@ -35,7 +68,13 @@ export default function Shadow() {
<div className="eaves-keraba-td">
<div className="outline-form">
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" defaultValue={100} />
<input
type="text"
className="input-origin block"
placeholder={0}
ref={refs.heightRef}
disabled={selectedType !== INPUT_TYPE.DIMENSION}
/>
</div>
<span className="thin">mm</span>
</div>
@ -46,4 +85,6 @@ export default function Shadow() {
</div>
</div>
)
}
})
export default Shadow

View File

@ -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 (
<>
<div className="discrimination-box mb10">
@ -18,7 +28,7 @@ export default function TriangleDormer() {
<div className="eaves-keraba-td">
<div className="outline-form">
<div className="input-grid mr5" style={{ width: '60px' }}>
<input type="text" className="input-origin block" defaultValue={2000} />
<input type="text" className="input-origin block" placeholder={0} ref={refs.heightRef} defaultValue={1500} />
</div>
<span className="thin">mm</span>
</div>
@ -29,18 +39,7 @@ export default function TriangleDormer() {
<div className="eaves-keraba-td">
<div className="outline-form">
<div className="input-grid mr5" style={{ width: '60px' }}>
<input type="text" className="input-origin block" defaultValue={1000} />
</div>
<span className="thin">mm</span>
</div>
</div>
</div>
<div className="eaves-keraba-item">
<div className="eaves-keraba-th">{getMessage('width')}</div>
<div className="eaves-keraba-td">
<div className="outline-form">
<div className="input-grid mr5" style={{ width: '60px' }}>
<input type="text" className="input-origin block" defaultValue={4000} />
<input type="text" className="input-origin block" placeholder={0} ref={refs.offsetRef} />
</div>
<span className="thin">mm</span>
</div>
@ -51,7 +50,7 @@ export default function TriangleDormer() {
<div className="eaves-keraba-td">
<div className="outline-form">
<div className="input-grid mr5" style={{ width: '60px' }}>
<input type="text" className="input-origin block" defaultValue={4} />
<input type="text" className="input-origin block" placeholder={0} ref={refs.pitchRef} defaultValue={4} />
</div>
<span className="thin"></span>
</div>
@ -69,13 +68,15 @@ export default function TriangleDormer() {
<span className="right">{getMessage('commons.east')}</span>
<span className="bottom">{getMessage('commons.south')}</span>
<span className="left">{getMessage('commons.west')}</span>
<button className="plane-btn up"></button>
<button className="plane-btn right"></button>
<button className="plane-btn down act"></button>
<button className="plane-btn left"></button>
<button className={`plane-btn up ${direction === 'up' ? ' act' : ''}`} value="up" onClick={getDirection}></button>
<button className={`plane-btn right ${direction === 'right' ? ' act' : ''}`} value="right" onClick={getDirection}></button>
<button className={`plane-btn down ${direction === 'down' ? ' act' : ''}`} value="down" onClick={getDirection}></button>
<button className={`plane-btn left ${direction === 'left' ? ' act' : ''}`} value="left" onClick={getDirection}></button>
</div>
</div>
</div>
</>
)
}
})
export default TriangleDormer

View File

@ -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 (
<WithDraggable isShow={true} pos={{ x: 50, y: -950 }}>

View File

@ -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) {
<button
className="btn-frame modal act"
onClick={() => {
handleFix(setShowOutlineModal)
handleFix()
setShowPropertiesSettingModal(true)
}}
>

View File

@ -8,6 +8,7 @@ import Diagonal from '@/components/floor-plan/modal/lineTypes/Diagonal'
import { useOuterLineWall } from '@/hooks/roofcover/useOuterLineWall'
import { OUTER_LINE_TYPE } from '@/store/outerLineAtom'
import OuterLineWall from '@/components/floor-plan/modal/lineTypes/OuterLineWall'
import { usePlacementShapeDrawing } from '@/hooks/surface/usePlacementShapeDrawing'
export default function PlacementShapeDrawing({ setShowPlaceShapeDrawingModal }) {
const { getMessage } = useMessage()
@ -45,7 +46,7 @@ export default function PlacementShapeDrawing({ setShowPlaceShapeDrawingModal })
outerLineDiagonalLengthRef,
handleRollback,
handleFix,
} = useOuterLineWall()
} = usePlacementShapeDrawing(setShowPlaceShapeDrawingModal)
const outerLineProps = {
length1,
@ -129,8 +130,12 @@ export default function PlacementShapeDrawing({ setShowPlaceShapeDrawingModal })
</div>
<div className="modal-body">
<div className="modal-btn-wrap">
{types.map((type) => (
<button className={`btn-frame modal ${buttonAct === type.id ? 'act' : ''}`} onClick={() => onClickButton(type)}>
{types.map((type, idx) => (
<button
key={`placement-${idx}`}
className={`btn-frame modal ${buttonAct === type.id ? 'act' : ''}`}
onClick={() => onClickButton(type)}
>
{type.name}
</button>
))}
@ -145,8 +150,12 @@ export default function PlacementShapeDrawing({ setShowPlaceShapeDrawingModal })
</div>
<div className="grid-btn-wrap">
<button className="btn-frame modal mr5">{getMessage('modal.cover.outline.rollback')}</button>
<button className="btn-frame modal act">{getMessage('modal.cover.outline.fix')}</button>
<button className="btn-frame modal mr5" onClick={handleRollback}>
{getMessage('modal.cover.outline.rollback')}
</button>
<button className="btn-frame modal act" onClick={handleFix}>
{getMessage('modal.cover.outline.fix')}
</button>
</div>
</div>
</div>

View File

@ -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),

View File

@ -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 (
<WithDraggable isShow={true} pos={{ x: 50, y: -950 }}>
@ -103,7 +39,7 @@ export default function RoofAllocationSetting({ setShowRoofAllocationSettingModa
{values.map((value, index) => (
<div className="grid-option-box" key={index}>
<div className="d-check-radio pop no-text">
<input type="radio" name="radio01" id="ra01" />
<input type="radio" name="radio01" />
<label htmlFor="ra01"></label>
</div>
<div className="grid-option-block-form">
@ -213,7 +149,9 @@ export default function RoofAllocationSetting({ setShowRoofAllocationSettingModa
))}
</div>
<div className="grid-btn-wrap">
<button className="btn-frame modal act">{getMessage('modal.roof.alloc.apply')}</button>
<button className="btn-frame modal act" onClick={handleSave}>
{getMessage('modal.roof.alloc.apply')}
</button>
</div>
</div>
</div>

View File

@ -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 (
<WithDraggable isShow={true} pos={{ x: 50, y: -950 }}>
<div className={`modal-pop-wrap xxm`}>
@ -25,7 +36,7 @@ export default function RoofShapePassivitySetting({ setShowRoofShapePassivitySet
<div className="modal-body">
<div className="modal-btn-wrap">
{buttons.map((button) => (
<button id={button.id} className={`btn-frame modal ${buttonAct === button.id ? 'act' : ''}`} onClick={() => setButtonAct(button.id)}>
<button key={button.id} className={`btn-frame modal ${type === button.type ? 'act' : ''}`} onClick={() => setType(button.type)}>
{button.name}
</button>
))}
@ -33,17 +44,23 @@ export default function RoofShapePassivitySetting({ setShowRoofShapePassivitySet
<div className="modal-bottom-border-bx">
<div className="setting-tit">{getMessage('setting')}</div>
<div className="discrimination-box">
{buttonAct === 1 && <Eaves />}
{buttonAct === 2 && <Gable />}
{buttonAct === 3 && <Shed />}
{type === TYPES.EAVES && <Eaves {...eavesProps} />}
{type === TYPES.GABLE && <Gable {...gableProps} />}
{type === TYPES.SHED && <Shed {...shedProps} />}
</div>
<div className="grid-btn-wrap">
<button className="btn-frame sub-tab mr5">{getMessage('common.setting.rollback')}</button>
<button className="btn-frame sub-tab act">{getMessage('apply')}</button>
<button className="btn-frame sub-tab mr5" onClick={handleRollback}>
{getMessage('common.setting.rollback')}
</button>
<button className="btn-frame sub-tab act" onClick={handleConfirm}>
{getMessage('apply')}
</button>
</div>
</div>
<div className="grid-btn-wrap">
<button className="btn-frame modal act">{getMessage('common.setting.finish')}</button>
<button className="btn-frame modal act" onClick={() => handleSave(setShowRoofShapePassivitySettingModal)}>
{getMessage('common.setting.finish')}
</button>
</div>
</div>
</div>

View File

@ -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 }

View File

@ -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')}
</span>
<div className="input-grid mr5">
<input type="text" className="input-origin block" defaultValue={100} />
<input type="text" className="input-origin block" defaultValue={4} ref={pitchRef} />
</div>
<span className="thin"></span>
</div>
@ -18,7 +18,7 @@ export default function Eaves() {
{getMessage('eaves.offset')}
</span>
<div className="input-grid mr5">
<input type="text" className="input-origin block" defaultValue={100} />
<input type="text" className="input-origin block" defaultValue={500} ref={offsetRef} />
</div>
<span className="thin">mm</span>
</div>

View File

@ -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')}
</span>
<div className="input-grid mr5">
<input type="text" className="input-origin block" defaultValue={100} />
<input type="text" className="input-origin block" defaultValue={4} ref={pitchRef} />
</div>
<span className="thin"></span>
</div>
@ -18,7 +18,7 @@ export default function Gable() {
{getMessage('gable.offset')}
</span>
<div className="input-grid mr5">
<input type="text" className="input-origin block" defaultValue={100} />
<input type="text" className="input-origin block" defaultValue={300} ref={offsetRef} />
</div>
<span className="thin">mm</span>
</div>

View File

@ -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')}
</span>
<div className="input-grid mr5">
<input type="text" className="input-origin block" defaultValue={100} />
<input type="text" className="input-origin block" defaultValue={300} ref={offsetRef} />
</div>
<span className="thin">mm</span>
</div>

View File

@ -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
</div>
<div className="properties-setting-wrap outer">
<div className="setting-tit">{getMessage('setting')}</div>
{type === TYPES.WALL_LINE_EDIT && <WallLine {...wallLineProps} />}
{type === TYPES.WALL_LINE_EDIT && <WallLine ref={wallLineEditRef} {...wallLineProps} />}
{type === TYPES.OFFSET && <Offset {...offsetProps} />}
</div>
<div className="grid-btn-wrap">

View File

@ -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 (
<>
<div className="outline-wrap">
@ -107,10 +56,10 @@ export default function WallLine({ length1Ref, length2Ref, arrow1Ref, arrow2Ref,
<div className="eaves-keraba-td">
<div className="outline-form">
<div className="grid-direction">
<button className={`direction up ${arrow1 === 'up' ? 'act' : ''} `} onClick={() => handleBtnClick('ArrowUp')}></button>
<button className={`direction down ${arrow1 === 'down' ? 'act' : ''} `} onClick={() => handleBtnClick('ArrowDown')}></button>
<button className={`direction left ${arrow1 === 'left' ? 'act' : ''} `} onClick={() => handleBtnClick('ArrowLeft')}></button>
<button className={`direction right ${arrow1 === 'right' ? 'act' : ''} `} onClick={() => handleBtnClick('ArrowRight')}></button>
<button className={`direction up ${arrow1 === 'up' ? 'act' : ''} `}></button>
<button className={`direction down ${arrow1 === 'down' ? 'act' : ''} `}></button>
<button className={`direction left ${arrow1 === 'left' ? 'act' : ''} `}></button>
<button className={`direction right ${arrow1 === 'right' ? 'act' : ''} `}></button>
</div>
</div>
</div>
@ -141,10 +90,10 @@ export default function WallLine({ length1Ref, length2Ref, arrow1Ref, arrow2Ref,
<div className="eaves-keraba-td">
<div className="outline-form">
<div className="grid-direction">
<button className={`direction up ${arrow2 === 'up' ? 'act' : ''} `} onClick={() => handleBtnClick('ArrowUp')}></button>
<button className={`direction down ${arrow2 === 'down' ? 'act' : ''} `} onClick={() => handleBtnClick('ArrowDown')}></button>
<button className={`direction left ${arrow2 === 'left' ? 'act' : ''} `} onClick={() => handleBtnClick('ArrowLeft')}></button>
<button className={`direction right ${arrow2 === 'right' ? 'act' : ''} `} onClick={() => handleBtnClick('ArrowRight')}></button>
<button className={`direction up ${arrow2 === 'up' ? 'act' : ''} `}></button>
<button className={`direction down ${arrow2 === 'down' ? 'act' : ''} `}></button>
<button className={`direction left ${arrow2 === 'left' ? 'act' : ''} `}></button>
<button className={`direction right ${arrow2 === 'right' ? 'act' : ''} `}></button>
</div>
</div>
</div>
@ -155,4 +104,4 @@ export default function WallLine({ length1Ref, length2Ref, arrow1Ref, arrow2Ref,
</div>
</>
)
}
})

View File

@ -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 ? (
<Link key={`${menu.id}`} href={menu.url}>
<Link key={`${menu.id}`} href={menu.url} scroll={false}>
{getMessage(menu.name)}
</Link>
) : (
@ -111,7 +147,9 @@ export default function Header(props) {
onMouseEnter={(e) => ToggleonMouse(e, 'add', 'li > ul')}
onMouseLeave={(e) => ToggleonMouse(e, 'remove', 'li > ul')}
>
<Link href={m.url}>{getMessage(m.name)}</Link>
<Link href={m.url} scroll={false}>
{getMessage(m.name)}
</Link>
</li>
)
})}
@ -137,9 +175,15 @@ export default function Header(props) {
</div>
<div className="header-left">
<div className="profile-box">
<Link href="/roof2">
<Link
href="#"
onClick={() => {
setUserInfoModal(true)
}}
>
<button className="profile">{userSession.userNm}</button>
</Link>
{userInfoModal && <UserInfoModal userId={sessionState.userId} userInfoModal={userInfoModal} setUserInfoModal={setUserInfoModal} />}
</div>
<div className="sign-out-box">
<button className="sign-out" onClick={() => logout()}>
@ -147,7 +191,7 @@ export default function Header(props) {
</button>
</div>
<div className="select-box">
<QSelectBox title={'Q.ORDER'} options={SelectOptions} onChange={onChangeSelect} />
<QSelectBox options={SelectOptions} onChange={onChangeSelect} />
</div>
<div className="btn-wrap">
<button className="btn-frame small dark" onClick={() => navPage()}>

View File

@ -1,10 +1,11 @@
import React from 'react'
import React, { useState } from 'react'
import { useMessage } from '@/hooks/useMessage'
import { useForm } from 'react-hook-form'
import { sessionStore } from '@/store/commonAtom'
import { useRecoilValue, useRecoilState } from 'recoil'
import { useAxios } from '@/hooks/useAxios'
import { globalLocaleStore } from '@/store/localeAtom'
import { useRouter } from 'next/navigation'
export default function ChangePasswordPop() {
const globalLocaleState = useRecoilValue(globalLocaleStore)
@ -12,7 +13,7 @@ export default function ChangePasswordPop() {
const { patch } = useAxios(globalLocaleState)
const { getMessage } = useMessage()
const [sessionState, setSessionState] = useRecoilState(sessionStore)
const router = useRouter()
const formInitValue = {
password1: '',
password2: '',
@ -74,6 +75,8 @@ export default function ChangePasswordPop() {
if (res.result.resultCode === 'S') {
alert(getMessage('main.popup.login.success'))
setSessionState({ ...sessionState, pwdInitYn: 'Y' })
//
router.push('/')
} else {
alert(res.result.resultMsg)
}
@ -154,7 +157,6 @@ export default function ChangePasswordPop() {
className="btn-origin grey"
onClick={() => {
router.push('/login')
setOpen(false)
}}
>
{getMessage('main.popup.login.btn2')}

View File

@ -15,6 +15,7 @@ import { convertNumberToPriceDecimal } from '@/util/common-utils'
import { appMessageStore, globalLocaleStore } from '@/store/localeAtom'
import KO from '@/locales/ko.json'
import JA from '@/locales/ja.json'
import QPagination from '../common/pagination/QPagination'
import '@/styles/grid.scss'
import { sessionStore } from '@/store/commonAtom'
@ -24,9 +25,9 @@ export default function Stuff() {
const stuffSearchParams = useRecoilValue(stuffSearchState)
const [stuffSearch, setStuffSearch] = useRecoilState(stuffSearchState)
const { getMessage } = useMessage()
const [curPage, setCurPage] = useState(1) //
const [defaultSize, setDefaultSize] = useState(100) //
const [gridCount, setGridCount] = useState(0) //
const [pageNo, setPageNo] = useState(1) //
const [pageSize, setPageSize] = useState(100) //
const [totalCount, setTotalCount] = useState(0) //
const [defaultSortType, setDefaultSortType] = useState('R')
const globalLocaleState = useRecoilValue(globalLocaleStore)
@ -53,10 +54,8 @@ export default function Stuff() {
const onDoubleClick = (e) => {
let objectNo = e.target.innerText
if (objectNo.substring(0, 1) === 'R') {
console.log('진짜')
router.push(`${pathname}/detail?objectNo=${objectNo.toString()}`)
} else {
console.log('임시')
router.push(`${pathname}/tempdetail?objectNo=${objectNo.toString()}`)
}
}
@ -64,28 +63,16 @@ export default function Stuff() {
const [gridProps, setGridProps] = useState({
gridData: [],
isPageable: false,
// sets 10 rows per page (default is 100)
// paginationPageSize: 100,
// allows the user to select the page size from a predefined list of page sizes
// paginationPageSizeSelector: [100, 200, 300, 400],
gridColumns: [
{
field: 'lastEditDatetime',
minWidth: 200,
headerName: getMessage('stuff.gridHeader.lastEditDatetime'),
headerCheckboxSelection: true,
headerCheckboxSelectionCurrentPageOnly: true, //
checkboxSelection: true,
showDisabledCheckboxes: true,
// .centered {
// .ag-header-cell-label {
// justify-content: center !important;
// }
// }
cellStyle: { textAlign: 'center' },
//suppressMovable: true, //
// width : 100
// minWidth : 100
// maxWidth : 100
valueFormatter: function (params) {
if (params.value) {
return dayjs(params?.value).format('YYYY.MM.DD HH:mm:ss')
@ -96,8 +83,8 @@ export default function Stuff() {
},
{
field: 'objectNo',
minWidth: 230,
headerName: getMessage('stuff.gridHeader.objectNo'),
// headerClass: 'centered', //_test.scss
cellRenderer: function (params) {
if (params.data.objectNo) {
return (
@ -131,7 +118,7 @@ export default function Stuff() {
headerName: getMessage('stuff.gridHeader.saleStoreId'),
cellStyle: { textAlign: 'left' },
},
{ field: 'saleStoreName', headerName: getMessage('stuff.gridHeader.saleStoreName'), cellStyle: { textAlign: 'left' } },
{ field: 'saleStoreName', minWidth: 300, headerName: getMessage('stuff.gridHeader.saleStoreName'), cellStyle: { textAlign: 'left' } },
{ field: 'address', headerName: getMessage('stuff.gridHeader.address'), cellStyle: { textAlign: 'left' } },
{ field: 'dispCompanyName', headerName: getMessage('stuff.gridHeader.dispCompanyName'), cellStyle: { textAlign: 'left' } },
{ field: 'receiveUser', headerName: getMessage('stuff.gridHeader.receiveUser'), cellStyle: { textAlign: 'left' } },
@ -168,14 +155,11 @@ export default function Stuff() {
if (event.column.colId === 'objectNo') {
return
} else {
console.log(' 상세이동::::::::', event.data)
//T R
if (event.data.objectNo) {
if (event.data.objectNo.substring(0, 1) === 'R') {
console.log('진짜:::::::::')
router.push(`${pathname}/detail?objectNo=${event.data.objectNo.toString()}`)
} else {
console.log('임시:::::::::::::::::')
router.push(`${pathname}/tempdetail?objectNo=${event.data.objectNo.toString()}`)
}
}
@ -242,56 +226,42 @@ export default function Stuff() {
//
useEffect(() => {
if (isObjectNotEmpty(sessionState)) {
// sessionState
if (stuffSearchParams?.code === 'S') {
const params = {
schObjectNo: '',
schAddress: '',
schObjectName: '',
schSaleStoreName: '',
schReceiveUser: '',
schDispCompanyName: '',
schDateType: 'U',
schObjectNo: stuffSearchParams?.schObjectNo,
schAddress: stuffSearchParams?.schAddress,
schObjectName: stuffSearchParams?.schObjectName,
schSaleStoreName: stuffSearchParams?.schSaleStoreName,
schReceiveUser: stuffSearchParams?.schReceiveUser,
schDispCompanyName: stuffSearchParams?.schDispCompanyName,
schDateType: stuffSearchParams.schDateType,
schFromDt: dayjs(new Date()).add(-1, 'year').format('YYYY-MM-DD'),
schToDt: dayjs(new Date()).format('YYYY-MM-DD'),
startRow: (curPage - 1) * defaultSize + 1,
endRow: curPage * defaultSize,
startRow: (pageNo - 1) * pageSize + 1,
endRow: pageNo * pageSize,
schSelSaleStoreId: '',
schSortType: 'R',
schSortType: stuffSearchParams.schSortType,
}
async function fetchData() {
//api startRow, endRow
// let startRow
// let endRow
// startRow = (curPage - 1) * size + 1
// endRow = curPage * size
// console.log('startrow::', startRow)
// console.log('endRow::', endRow)
// let curPage
// let totalpage
// let totalCount
// let size
// let pageCount
// console.log(' ::::::::::', sessionState)
// const apiUrl = `/api/object/list?saleStoreId=201TES01&${queryStringFormatter(params)}`
// const apiUrl = `/api/object/list?saleStoreId=X167&${queryStringFormatter(params)}`
const apiUrl = `/api/object/list?saleStoreId=${sessionState?.storeId}&${queryStringFormatter(params)}`
// const apiUrl = `/api/object/list?saleStoreId=${sessionState?.storeId}&${queryStringFormatter(params)}`
const apiUrl = `/api/object/list?saleStoreId=T01&${queryStringFormatter(params)}`
await get({
url: apiUrl,
}).then((res) => {
if (!isEmptyArray(res)) {
setGridProps({ ...gridProps, gridData: res, count: res[0].totCnt })
setGridCount(res[0].totCnt)
setTotalCount(res[0].totCnt)
}
})
}
fetchData()
} else {
//
const params = {
schObjectNo: '',
schObjectNo: stuffSearchParams.schObjectNo,
schAddress: '',
schObjectName: '',
schSaleStoreName: '',
@ -300,30 +270,13 @@ export default function Stuff() {
schDateType: 'U',
schFromDt: dayjs(new Date()).add(-1, 'year').format('YYYY-MM-DD'),
schToDt: dayjs(new Date()).format('YYYY-MM-DD'),
startRow: (curPage - 1) * defaultSize + 1,
endRow: curPage * defaultSize,
startRow: (pageNo - 1) * pageSize + 1,
endRow: pageNo * pageSize,
schSelSaleStoreId: '',
schSortType: 'R',
}
async function fetchData() {
//api startRow, endRow
// let startRow
// let endRow
// startRow = (curPage - 1) * size + 1
// endRow = curPage * size
// console.log('startrow::', startRow)
// console.log('endRow::', endRow)
// let curPage
// let totalpage
// let totalCount
// let size
// let pageCount
// console.log(' ::::::::::', sessionState)
// const apiUrl = `/api/object/list?saleStoreId=201TES01&${queryStringFormatter(params)}`
// const apiUrl = `/api/object/list?saleStoreId=X167&${queryStringFormatter(params)}`
const apiUrl = `/api/object/list?saleStoreId=${sessionState?.storeId}&${queryStringFormatter(params)}`
await get({
@ -331,34 +284,34 @@ export default function Stuff() {
}).then((res) => {
if (!isEmptyArray(res)) {
setGridProps({ ...gridProps, gridData: res, count: res[0].totCnt })
setGridCount(res[0].totCnt)
setTotalCount(res[0].totCnt)
}
})
}
fetchData()
}
}
}, [sessionState])
}, [pageNo, sessionState])
useEffect(() => {
if (stuffSearchParams?.code === 'E') {
//console.log('::::::::', stuffSearchParams)
stuffSearchParams.startRow = (curPage - 1) * defaultSize + 1
stuffSearchParams.endRow = curPage * defaultSize
//console.log('::::::::', stuffSearchParams, sessionState)
stuffSearchParams.startRow = 1
stuffSearchParams.endRow = 1 * pageSize
stuffSearchParams.schSortType = defaultSortType
setPageNo(1)
async function fetchData() {
// console.log(' :::::::::::::', sessionState)
// const apiUrl = `/api/object/list?saleStoreId=201TES01&${queryStringFormatter(stuffSearchParams)}`
// const apiUrl = `/api/object/list?saleStoreId=X167&${queryStringFormatter(stuffSearchParams)}`
const apiUrl = `/api/object/list?saleStoreId=${sessionState?.storeId}&${queryStringFormatter(stuffSearchParams)}`
const apiUrl = `/api/object/list?saleStoreId=T01&${queryStringFormatter(stuffSearchParams)}`
// const apiUrl = `/api/object/list?saleStoreId=${sessionState?.storeId}&${queryStringFormatter(stuffSearchParams)}`
await get({ url: apiUrl }).then((res) => {
// console.log(' API:::::::', res)
if (!isEmptyArray(res)) {
setGridProps({ ...gridProps, gridData: res, count: res[0].totCnt })
setGridCount(res[0].totCnt)
setTotalCount(res[0].totCnt)
} else {
setGridProps({ ...gridProps, gridData: [], count: 0 })
setGridCount(0)
setTotalCount(0)
}
})
}
@ -368,53 +321,58 @@ export default function Stuff() {
//
const onChangePerPage = (e) => {
let startRow = (curPage - 1) * e.target.value + 1
let startRow = (1 - 1) * e.target.value + 1
stuffSearchParams.startRow = startRow
stuffSearchParams.endRow = curPage * e.target.value
setDefaultSize(e.target.value)
stuffSearchParams.endRow = 1 * e.target.value
setPageSize(e.target.value)
setStuffSearch({
...stuffSearch,
code: 'S',
startRow: startRow,
endRow: curPage * e.target.value,
endRow: 1 * e.target.value,
})
// console.log(' :::', stuffSearchParams)
// console.log(' sessionState:::', sessionState)
// const apiUrl = `/api/object/list?saleStoreId=201TES01&${queryStringFormatter(stuffSearchParams)}`
// const apiUrl = `/api/object/list?saleStoreId=X167&${queryStringFormatter(stuffSearchParams)}`
const apiUrl = `/api/object/list?saleStoreId=${sessionState?.storeId}&${queryStringFormatter(stuffSearchParams)}`
setPageNo(1)
// const apiUrl = `/api/object/list?saleStoreId=${sessionState?.storeId}&${queryStringFormatter(stuffSearchParams)}`
const apiUrl = `/api/object/list?saleStoreId=T01&${queryStringFormatter(stuffSearchParams)}`
get({ url: apiUrl }).then((res) => {
if (!isEmptyArray(res)) {
setGridProps({ ...gridProps, gridData: res, count: res[0].totCnt })
setGridCount(res[0].totCnt)
setTotalCount(res[0].totCnt)
} else {
setGridProps({ ...gridProps, gridData: [], count: 0 })
setGridCount(0)
setTotalCount(0)
}
})
}
//
const onChangeSortType = (e) => {
let startRow = (1 - 1) * pageSize + 1
stuffSearchParams.startRow = startRow
stuffSearchParams.endRow = 1 * pageSize
stuffSearchParams.schSortType = e.target.value
// console.log(' :::', stuffSearchParams)
setDefaultSortType(e.target.value)
setStuffSearch({
...stuffSearch,
code: 'S',
startRow: startRow,
endRow: 1 * pageSize,
schSortType: e.target.value,
})
// console.log(' ::::::::::::', sessionState)
// const apiUrl = `/api/object/list?saleStoreId=201TES01&${queryStringFormatter(stuffSearchParams)}`
// const apiUrl = `/api/object/list?saleStoreId=X167&${queryStringFormatter(stuffSearchParams)}`
const apiUrl = `/api/object/list?saleStoreId=${sessionState?.storeId}&${queryStringFormatter(stuffSearchParams)}`
setPageNo(1)
const apiUrl = `/api/object/list?saleStoreId=T01&${queryStringFormatter(stuffSearchParams)}`
// const apiUrl = `/api/object/list?saleStoreId=${sessionState?.storeId}&${queryStringFormatter(stuffSearchParams)}`
get({ url: apiUrl }).then((res) => {
if (!isEmptyArray(res)) {
setGridProps({ ...gridProps, gridData: res, count: res[0].totCnt })
setGridCount(res[0].totCnt)
setTotalCount(res[0].totCnt)
} else {
setGridProps({ ...gridProps, gridData: [], count: 0 })
setGridCount(0)
setTotalCount(0)
}
})
}
@ -427,20 +385,34 @@ export default function Stuff() {
}
}, [globalLocaleState])
//
const handleChangePage = (page) => {
stuffSearchParams.code = 'S'
setStuffSearch({
...stuffSearch,
code: 'S',
startRow: (page - 1) * pageSize + 1,
endRow: page * pageSize,
})
setPageNo(page)
}
return (
<>
{/* 퍼블시작 */}
<div className="sub-table-box">
<div className="table-box-title-wrap">
<div className="title-wrap">
<h3>물건목록</h3>
<h3>{getMessage('stuff.search.grid.title')}</h3>
<ul className="info-wrap">
<li>
전체
<span>{convertNumberToPriceDecimal(gridCount)}</span>
{getMessage('stuff.search.grid.all')}
<span>{convertNumberToPriceDecimal(totalCount)}</span>
</li>
<li>
선택
{getMessage('stuff.search.grid.selected')}
<span className="red">{convertNumberToPriceDecimal(selectedRowDataCount)}</span>
</li>
</ul>
@ -448,8 +420,8 @@ export default function Stuff() {
<div className="left-unit-box">
<div className="select-box mr5" style={{ width: '110px' }}>
<select className="select-light black" name="" id="" onChange={onChangeSortType}>
<option value="R">최근 등록일</option>
<option value="U">최근 수정일</option>
<option value="R">{getMessage('stuff.search.grid.schSortTypeR')}</option>
<option value="U">{getMessage('stuff.search.grid.schSortTypeU')}</option>
</select>
</div>
<div className="select-box" style={{ width: '80px' }}>
@ -464,7 +436,9 @@ export default function Stuff() {
<div className="grid-table-wrap">
<div className="q-grid">
<StuffQGrid {...gridProps} getSelectedRowdata={getSelectedRowdata} getCellDoubleClicked={getCellDoubleClicked} gridRef={gridRef} />
<div className="pagination-wrap">페이징 컴포넌트예정</div>
<div className="pagination-wrap">
<QPagination pageNo={pageNo} pageSize={pageSize} pagePerBlock={10} totalCount={totalCount} handleChangePage={handleChangePage} />
</div>
</div>
</div>
</div>

View File

@ -1,23 +1,30 @@
'use client'
import React, { useState, useEffect } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import React, { useState, useEffect, useRef } from 'react'
import { useRouter, useSearchParams, usePathname } from 'next/navigation'
import { Button } from '@nextui-org/react'
import Select from 'react-dropdown-select'
import Select from 'react-select'
import Link from 'next/link'
import { useAxios } from '@/hooks/useAxios'
import { globalLocaleStore } from '@/store/localeAtom'
import { queryStringFormatter, isEmptyArray } from '@/util/common-utils'
import { isEmptyArray, isNotEmptyArray, isObjectNotEmpty } from '@/util/common-utils'
import { useMessage } from '@/hooks/useMessage'
import { useForm } from 'react-hook-form'
import { useRecoilValue } from 'recoil'
import { sessionStore } from '@/store/commonAtom'
import FindAddressPop from './popup/FindAddressPop'
import PlanRequestPop from './popup/PlanRequestPop'
import WindSelectPop from './popup/WindSelectPop'
export default function StuffDetail() {
const sessionState = useRecoilValue(sessionStore)
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const { getMessage } = useMessage()
const globalLocaleState = useRecoilValue(globalLocaleStore)
const { get, post, del } = useAxios(globalLocaleState)
const ref = useRef()
const { get, post, del, promisePost } = useAxios(globalLocaleState)
//form
const formInitValue = {
// T...() R...()
@ -26,16 +33,17 @@ export default function StuffDetail() {
objectName: '', //
objectNameOmit: '', //
objectNameKana: '', //
saleStoreId: '', //ID
saleStoreName: '', //
otherSaleStoreId: '',
otherSaleStoreName: '',
saleStoreLevel: '', //1
saleStoreId: '', //1ID
saleStoreName: '', //1
otherSaleStoreId: '', //1 ID
otherSaleStoreName: '', //1
otherSaleStoreLevel: '', //1
zipNo: '', //
prefId: '', //
prefName: '',
address: '', //
areaId: '', //id
// areaName: '', //
windSpeed: '', //
verticalSnowCover: '', //NEW
coldRegionFlg: false, //(true : 1 / false : 0)
@ -56,20 +64,25 @@ export default function StuffDetail() {
const [prefValue, setPrefValue] = useState('')
const [saleStoreList, setSaleStoreList] = useState([]) //
const [otherSaleStoreList, setOtherSaleStoreList] = useState([])
const [originOtherSaleStoreList, setOriginOtherSaleStoreList] = useState([])
const [areaIdList, setAreaIdList] = useState([]) //
const [windSpeedList, setWindSpeedList] = useState([]) //
// const [windSpeedList, setWindSpeedList] = useState([]) //
const [isFormValid, setIsFormValid] = useState(false) //,
const [buttonValid, setButtonValid] = useState(false) //
const [showAddressButtonValid, setShowAddressButtonValid] = useState(false) //
const [showDesignRequestButtonValid, setShowDesignRequestButtonValid] = useState(false) //
const [showWindSpeedButtonValid, setShowWindSpeedButtonValid] = useState(false) //
const objectNo = searchParams.get('objectNo') //url set
const [editMode, setEditMode] = useState('NEW')
const [detailData, setDetailData] = useState({})
useEffect(() => {
// console.log('objectNo::', objectNo)
if (objectNo) {
//console.log('')
console.log('수정화면')
setEditMode('EDIT')
if (objectNo.substring(0, 1) === 'R') {
@ -77,33 +90,9 @@ export default function StuffDetail() {
setIsFormValid(true)
}
get({ url: `/api/object/${objectNo}/detail` }).then((res) => {
// console.log(' API ')
console.log('물건번호로 상세 API 호출')
if (res != null) {
setDetailData(res)
// APi
// API
get({ url: '/api/object/prefecture/list' }).then((res) => {
if (!isEmptyArray(res)) {
// console.log('API :::', res)
setPrefCodeList(res)
}
})
// API /api/object/saleStore//list -
// 1 saleStoreId=201TES01
// T01
//1 : X167
get({ url: `/api/object/saleStore/X167/list` }).then((res) => {
if (!isEmptyArray(res)) {
// console.log(' :::::', res)
setSaleStoreList(res)
//1
form.setValue('saleStoreId', res[0].saleStoreId)
//1
form.setValue('saleStoreName', res[0].saleStoreId)
setOtherSaleStoreList([])
}
})
}
})
} else {
@ -115,60 +104,121 @@ export default function StuffDetail() {
setPrefCodeList(res)
}
})
// API /api/object/saleStore//list -
// 1 saleStoreId=201TES01
// T01
//1 : X167
get({ url: `/api/object/saleStore/X167/list` }).then((res) => {
get({ url: `/api/object/saleStore/T01/list` }).then((res) => {
// get({ url: `/api/object/saleStore/${sessionState?.storeId}/list` }).then((res) => {
if (!isEmptyArray(res)) {
// console.log(' :::::', res)
const firstList = res.filter((row) => row.saleStoreLevel === '1')
const otherList = res.filter((row) => row.saleStoreLevel !== '1')
// console.log('first:::::', firstList)
// console.log('otherList:::::', otherList)
//1
setSaleStoreList(firstList)
//1
form.setValue('saleStoreId', firstList[0].saleStoreId)
//1
form.setValue('saleStoreName', firstList[0].saleStoreId)
//1
setOriginOtherSaleStoreList(otherList)
setOtherSaleStoreList(otherList)
form.setValue('otherSaleStoreId', otherList[0].saleStoreId)
form.setValue('otherSaleStoreName', otherList[0].saleStoreId)
}
})
}
}, [objectNo])
useEffect(() => {
if (isObjectNotEmpty(detailData)) {
console.log('상세데이타:::::::', detailData)
// API
get({ url: '/api/object/prefecture/list' }).then((res) => {
if (!isEmptyArray(res)) {
// console.log('API :::', res)
setPrefCodeList(res)
}
})
// 1 ??
// 1 saleStoreId=201TES01
// T01
//1 : X167
get({ url: `/api/object/saleStore/T01/list` }).then((res) => {
// get({ url: `/api/object/saleStore/${sessionState?.storeId}/list` }).then((res) => {
if (!isEmptyArray(res)) {
const firstList = res.filter((row) => row.saleStoreLevel === '1')
const otherList = res.filter((row) => row.saleStoreLevel !== '1')
//1
setSaleStoreList(firstList)
//1
setOriginOtherSaleStoreList(otherList)
setOtherSaleStoreList(otherList)
}
})
}
}, [detailData])
//1
const onSelectionChange = (key) => {
if (key == null) {
if (isObjectNotEmpty(key)) {
setOtherSaleStoreList(otherSaleStoreList)
form.setValue('saleStoreId', key.saleStoreId)
form.setValue('saleStoreName', key.saleStoreName)
form.setValue('saleStoreLevel', key.saleStoreLevel)
// 1 2 list
// 
let newOtherSaleStoreList = originOtherSaleStoreList.filter((row) => row.firstAgentId === key.saleStoreId)
setOtherSaleStoreList(newOtherSaleStoreList)
} else {
//X
form.setValue('saleStoreId', '')
form.setValue('saleStoreName', '')
} else {
const name = saleStoreList.find((obj) => obj.saleStoreId === key.target.value).saleStoreName
form.setValue('saleStoreId', key.target.value)
form.setValue('saleStoreName', name)
form.setValue('saleStoreLevel', '')
form.setValue('otherSaleStoreId', '')
form.setValue('otherSaleStoreName', '')
form.setValue('otherSaleStoreLevel', '')
setOtherSaleStoreList(originOtherSaleStoreList)
//1 2
handleClear()
}
}
//2
const onSelectionChange2 = (key) => {
const name = otherSaleStoreList.find((obj) => obj.saleStoreId === key.target.value).saleStoreName
form.setValue('otherSaleStoreId', key.target.value)
form.setValue('otherSaleStoreNm', name)
}
//
const _zipNo = watch('zipNo')
useEffect(() => {
if (_zipNo !== '' && _zipNo.length === 7 && !_zipNo.match(/\D/g)) {
setButtonValid(true)
if (isObjectNotEmpty(key)) {
form.setValue('otherSaleStoreId', key.saleStoreId)
form.setValue('otherSaleStoreName', key.saleStoreName)
form.setValue('otherSaleStoreLevel', key.saleStoreLevel)
} else {
setButtonValid(false)
form.setValue('otherSaleStoreId', '')
form.setValue('otherSaleStoreName', '')
form.setValue('otherSaleStoreLevel', '')
}
}, [_zipNo])
}
//1 2
const handleClear = () => {
if (ref.current) {
ref.current.clearValue()
}
}
//
const setZipInfo = (info) => {
// console.log(' ::::::::', info)
setPrefValue(info.prefId)
form.setValue('prefId', info.prefId)
form.setValue('prefName', info.address1)
form.setValue('address', info.address2 + info.address3)
form.setValue('zipNo', info.zipNo)
}
//
const setPlanReqInfo = (info) => {
console.log('팝업에서 넘어온 설계의뢰 정보::: ', info)
// form.setValue('dispCompanyName', info.planReqName)
}
//
const setWindSppedInfo = (info) => {
form.setValue('windSpeed', info.windSpeed)
}
//
// dispCompanyName: '', //
@ -192,7 +242,7 @@ export default function StuffDetail() {
const _objectName = watch('objectName')
const _objectNameOmit = watch('objectNameOmit')
const _saleStoreId = watch('saleStoreId')
const _otherSaleStoreId = watch('otherSaleStoreId')
const _saleStoreLevel = watch('saleStoreLevel')
const _prefId = watch('prefId')
const _address = watch('address')
const _areaId = watch('areaId') //new
@ -201,68 +251,56 @@ export default function StuffDetail() {
const _installHeight = watch('installHeight')
useEffect(() => {
// console.log('mode:::::', editMode)
if (editMode === 'NEW') {
const formData = form.getValues()
// console.log('::::::::::::', formData)
// console.log('::::::::::::', formData)
let errors = {}
if (!_dispCompanyName || _dispCompanyName.trim().length === 0) {
if (!formData.dispCompanyName || formData.dispCompanyName.trim().length === 0) {
errors.dispCompanyName = true
}
if (!_objectName || _objectName.trim().length === 0) {
if (!formData.objectName || formData.objectName.trim().length === 0) {
errors.objectName = true
}
if (!_objectNameOmit) {
if (!formData.objectNameOmit) {
errors.objectNameOmit = true
}
if (!_saleStoreId) {
if (!formData.saleStoreId) {
errors.saleStoreId = true
}
// if (!_otherSaleStoreId) {
// errors.otherSaleStoreId = true
// }
if (!_zipNo || _zipNo.length != 7) {
errors.zipCode = true
}
if (!_prefId) {
if (!formData.prefId) {
errors.prefId = true
}
if (!_address.trim().length === 0) {
errors.address = true
}
if (!_areaId) {
if (!formData.areaId) {
errors.areaId = true
}
if (!_windSpeed) {
if (!formData.windSpeed) {
errors.windSpeed = true
}
if (!_verticalSnowCover) {
if (!formData.verticalSnowCover) {
errors.verticalSnowCover = true
}
if (!_installHeight) {
if (!formData.installHeight) {
errors.installHeight = true
}
// console.log('errors::', errors)
// console.log('::', errors)
setIsFormValid(Object.keys(errors).length === 0)
} else {
// console.log(' ')
console.log('상세일때 폼체크')
}
}, [
_dispCompanyName,
_objectName,
_objectNameOmit,
_saleStoreId,
_saleStoreLevel,
// _otherSaleStoreId,
_zipNo,
// _otherSaleStoreLevel,
_prefId,
_address,
_areaId,
@ -271,52 +309,33 @@ export default function StuffDetail() {
_installHeight,
])
// API
const onSearchPostNumber = () => {
const params = {
zipcode: _zipNo,
}
//
const onSearchPostNumberPopOpen = () => {
setShowAddressButtonValid(true)
}
get({ url: `https://zipcloud.ibsnet.co.jp/api/search?${queryStringFormatter(params)}` }).then((res) => {
//7830060
//9302226
//0790177 3
if (res.status === 200) {
if (res.results != null) {
console.log('주소검색::', res.results)
// console.log('prefcode::', res.results[0].prefcode)
// console.log('address::', res.results[0].address2 + res.results[0].address3)
setPrefValue(res.results[0].prefcode)
form.setValue('prefId', res.results[0].prefcode)
form.setValue('prefName', res.results[0].address1)
form.setValue('address', res.results[0].address2 + res.results[0].address3)
} else {
alert('등록된 우편번호에서 주소를 찾을 수 없습니다. 다시 입력해주세요.')
form.setValue('prefId', '')
form.setValue('prefName', '')
form.setValue('address', '')
form.setValue('zipNo', '')
setPrefValue('')
setAreaIdList([])
form.setValue('areaId', '')
// form.setValue('areaName', '')
setWindSpeedList([])
form.setValue('windSpeed', '')
}
} else {
alert(res.message)
}
})
//
const onSearchDesignRequestPopOpen = () => {
setShowDesignRequestButtonValid(true)
}
//
const onSearchWindSpeedPopOpen = () => {
const prefName = form.watch('prefName')
if (prefName === '') {
alert(getMessage('stuff.windSelectPopup.error.message1'))
} else {
setShowWindSpeedButtonValid(true)
}
}
useEffect(() => {
if (prefValue !== '') {
//
// /api/object/prefecture//list
get({ url: `/api/object/prefecture/${prefValue}/list` }).then((res) => {
if (!isEmptyArray(res)) {
// console.log(' ::::::::', res)
form.setValue('areaId', res[0].prefId)
form.setValue('areaId', res[0].areaId)
form.setValue('areaName', res[0].prefName)
setAreaIdList(res)
}
@ -329,20 +348,18 @@ export default function StuffDetail() {
form.setValue('areaId', e.target.value)
}
useEffect(() => {
if (!isEmptyArray(areaIdList)) {
// ->
console.log('prefName::', form.watch('prefName'))
let _prefName = form.watch('prefName')
//http://localhost:8080/api/object/windSpeed//list
get({ url: `/api/object/windSpeed/${_prefName}/list` }).then((res) => {
if (!isEmptyArray(res)) {
// console.log(':::::::::', res)
setWindSpeedList(res)
}
})
}
}, [areaIdList])
// useEffect(() => {
// if (!isEmptyArray(areaIdList)) {
// let _prefName = form.watch('prefName')
// // console.log(' API', _prefName)
// get({ url: `/api/object/windSpeed/${_prefName}/list` }).then((res) => {
// // console.log('res::', res)
// if (!isEmptyArray(res)) {
// setWindSpeedList(res)
// }
// })
// }
// }, [areaIdList])
//
const onValid = (data) => {
@ -378,10 +395,11 @@ export default function StuffDetail() {
//
const onTempSave = async () => {
const formData = form.getValues()
// console.log('formData::', formData)
const params = {
saleStoreId: formData.saleStoreId,
saleStoreName: formData.saleStoreName,
saleStoreId: formData.otherSaleStoreId ? formData.otherSaleStoreId : formData.saleStoreId,
saleStoreName: formData.otherSaleStoreName ? formData.otherSaleStoreName : formData.saleStoreName,
saleStoreLevel: formData.otherSaleStoreLevel ? formData.otherSaleStoreLevel : formData.saleStoreLevel,
objectStatusId: formData.objectStatusId,
objectName: formData.objectName,
objectNameOmit: formData.objectNameOmit,
@ -403,10 +421,17 @@ export default function StuffDetail() {
workNo: null,
workName: null,
}
console.log('임시저장params::', params)
return
await post({ url: '/api/object/save-object', data: params }).then((res) => {
console.log('res::::::', res)
//1 or 2
if (params.saleStoreId == '') {
params.saleStoreId = sessionState.storeId
params.saleStoreLevel = sessionState.storeLvl
}
await promisePost({ url: '/api/object/save-object', data: params }).then((res) => {
if (res.status === 201) {
alert('임시저장 되었습니다. 물건번호를 획득하려면 필수 항목을 모두 입력해 주십시오.')
router.push(`${pathname}?objectNo=${res.data.objectNo.toString()}`)
}
})
}
@ -419,7 +444,7 @@ export default function StuffDetail() {
let testobj = '10'
del({ url: `/api/object/${testobj}` }).then((res) => {
// console.log(' :::', res)
console.log('삭제 결과:::', res)
router.push('/management/stuff')
})
}
@ -431,7 +456,8 @@ export default function StuffDetail() {
<form onSubmit={handleSubmit(onValid)}>
<div className="sub-table-box">
<div className="promise-gudie">
<span className="important">*</span> 필수 입력항목
<span className="important">*</span>
{getMessage('stuff.detail.required')}
</div>
<div className="infomation-table">
<table>
@ -440,9 +466,22 @@ export default function StuffDetail() {
<col />
</colgroup>
<tbody>
<tr>
<th>{getMessage('stuff.detail.planReqNo')}</th>
<td>
<div className="flx-box">
<div className="input-wrap mr5" style={{ width: '200px' }}>
<input type="text" className="input-light" readOnly />
</div>
<button className="btn-origin grey" onClick={onSearchDesignRequestPopOpen}>
{getMessage('stuff.planReqPopup.title')}
</button>
</div>
</td>
</tr>
<tr>
<th>
담당자 <span className="important">*</span>
{getMessage('stuff.detail.dispCompanyName')} <span className="important">*</span>
</th>
<td>
<div className="input-wrap" style={{ width: '500px' }}>
@ -452,24 +491,24 @@ export default function StuffDetail() {
</tr>
<tr>
<th>
물건구분/물건명 <span className="important">*</span>
{getMessage('stuff.detail.objectStatusId')} <span className="important">*</span>
</th>
<td>
<div className="flx-box">
<div className="d-check-radio light mr10">
<input type="radio" name="objectStatusId" value="0" id="objectStatus0" {...form.register('objectStatusId')} />
<label htmlFor="objectStatus0">신축</label>
<label htmlFor="objectStatus0">{getMessage('stuff.detail.objectStatus0')}</label>
</div>
<div className="d-check-radio light mr10">
<input type="radio" name="objectStatusId" value="1" id="objectStatus1" {...form.register('objectStatusId')} />
<label htmlFor="objectStatus0">기축</label>
<label htmlFor="objectStatus1">{getMessage('stuff.detail.objectStatus1')}</label>
</div>
<div className="input-wrap mr5" style={{ width: '545px' }}>
<input type="text" className="input-light" {...form.register('objectName')} />
</div>
<div className="select-wrap" style={{ width: '120px' }}>
<select className="select-light" name="objectNameOmit" {...register('objectNameOmit')}>
<option value="">경칭공통코드선택</option>
<option value="">경칭선택</option>
<option value="11">경칭11</option>
<option value="22">경칭22</option>
<option value="33">경칭33</option>
@ -479,7 +518,7 @@ export default function StuffDetail() {
</td>
</tr>
<tr>
<th>물건명 후리가나</th>
<th>{getMessage('stuff.detail.objectNameKana')}</th>
<td>
<div className="input-wrap" style={{ width: '789px' }}>
<input type="text" className="input-light" {...form.register('objectNameKana')} />
@ -490,26 +529,27 @@ export default function StuffDetail() {
<th>
<div className="flx-box">
<div className="title">
1 판매점명 / ID
{getMessage('stuff.detail.saleStoreId')}
<span className="important">*</span>
</div>
<div className="tooltips"></div>
<div className="tooltips">
<span>{getMessage('stuff.detail.tooltip.saleStoreId')}</span>
</div>
</div>
</th>
<td>
<div className="flx-box">
<div className="select-wrap mr5" style={{ width: '567px' }}>
{saleStoreList?.length > 0 && (
<select className="select-light" onChange={onSelectionChange} value={form.watch('saleStoreId')}>
{saleStoreList.map((row) => {
return (
<option key={row.saleStoreLevel} value={row.saleStoreId}>
{row.saleStoreName}
</option>
)
})}
</select>
)}
<Select
className="react-select-custom"
classNamePrefix="custom"
placeholder="Select"
options={saleStoreList}
onChange={onSelectionChange}
getOptionLabel={(x) => x.saleStoreName}
getOptionValue={(x) => x.saleStoreId}
isClearable={true}
/>
</div>
<div className="input-wrap" style={{ width: '216px' }}>
<input type="text" className="input-light" value={form.watch('saleStoreId')} {...form.register('saleStoreId')} readOnly />
@ -520,24 +560,27 @@ export default function StuffDetail() {
<tr>
<th>
<div className="flx-box">
<div className="title">2 판매점명 / ID</div>
<div className="tooltips"></div>
<div className="title">{getMessage('stuff.detail.otherSaleStoreId')}</div>
<div className="tooltips">
<span>{getMessage('stuff.detail.tooltip.saleStoreId')}</span>
</div>
</div>
</th>
<td>
<div className="flx-box">
<div className="select-wrap mr5" style={{ width: '567px' }}>
{otherSaleStoreList?.length > 0 && (
<select className="select-light" onChange={onSelectionChange2} value={form.watch('otherSaleStoreId')}>
{otherSaleStoreList.map((row) => {
return (
<option key={row.saleStoreId} value={row.saleStoreId} text={row.saleStoreName}>
{row.saleStoreName}
</option>
)
})}
</select>
)}
<Select
className="react-select-custom"
classNamePrefix="custom"
placeholder="Select"
ref={ref}
options={otherSaleStoreList}
onChange={onSelectionChange2}
getOptionLabel={(x) => x.saleStoreName}
getOptionValue={(x) => x.saleStoreId}
isDisabled={form.watch('saleStoreId') !== '' ? false : true}
isClearable={true}
/>
</div>
<div className="input-wrap" style={{ width: '216px' }}>
<input
@ -553,48 +596,37 @@ export default function StuffDetail() {
</tr>
<tr>
<th>
우편번호 <span className="important">*</span>
{getMessage('stuff.detail.zipNo')} <span className="important">*</span>
</th>
<td>
<div className="flx-box">
<div className="input-wrap mr5" style={{ width: '200px' }}>
<input
type="text"
className="input-light"
maxLength={7}
{...form.register('zipNo', {
minLength: { value: 7, message: '7자리만가능' },
pattern: { value: /^[0-9]*$/g, message: '숫자만 입력' },
})}
/>
<input type="text" className="input-light" disabled value={form.watch('zipNo')} />
</div>
<Button className="btn-origin grey" isDisabled={!buttonValid} onClick={onSearchPostNumber}>
주소검색
<Button className="btn-origin grey" onClick={onSearchPostNumberPopOpen}>
{getMessage('stuff.detail.btn.addressPop')}
</Button>
<div className="guide">*우편번호 7자리를 입력한 , 주소검색 버튼을 클릭해 주십시오</div>
<div className="guide">{getMessage('stuff.detail.btn.addressPop.guide')}</div>
</div>
</td>
</tr>
<tr>
<th>
도도부현 / 주소 <span className="important">*</span>
{getMessage('stuff.detail.prefId')}
<span className="important">*</span>
</th>
<td>
<div className="flx-box">
<div className="input-wrap mr5" style={{ width: '200px' }}>
<input type="text" className="input-light" value={form.watch('prefName')} {...form.register('prefName')} readOnly />
{/* {prefCodeList?.length > 0 && (
<Select className="max-w-xs" selectedKeys={prefValue} isDisabled {...form.register('prefId')}>
{prefCodeList.map((row) => {
return <SelectItem key={row.prefId}>{row.prefName}</SelectItem>
})}
</Select>
)} */}
{/* {prefCodeList?.length > 0 && (
<select>
<option value=""></option>
<div className="select-wrap" style={{ width: '200px' }}>
{prefCodeList?.length > 0 && (
<select className="select-light" name="prefName" {...register('prefId')} disabled>
{prefCodeList.map((row) => (
<option key={row.prefId} value={row.prefId}>
{row.prefName}
</option>
))}
</select>
)} */}
)}
</div>
<div className="input-wrap mr5" style={{ width: '580px' }}>
<input type="text" className="input-light" value={form.watch('address')} {...form.register('address')} />
@ -604,17 +636,15 @@ export default function StuffDetail() {
</tr>
<tr>
<th>
발전량시뮬레이션지역 <span className="important">*</span>
{getMessage('stuff.detail.areaId')} <span className="important">*</span>
</th>
<td>
<div className="select-wrap" style={{ width: '200px' }}>
<select
className="select-light"
name="areaId"
onChange={(e) => {
form.setValue('areaId', e.target.value)
}}
disabled={areaIdList?.length > 0 ? false : true}
onChange={handleAreaIdOnChange}
>
{areaIdList.map((row) => {
return (
@ -629,11 +659,14 @@ export default function StuffDetail() {
</tr>
<tr>
<th>
기준풍속 <span className="important">*</span>
{getMessage('stuff.detail.windSpeed')} <span className="important">*</span>
</th>
<td>
<div className="flx-box">
<div className="select-wrap mr10" style={{ width: '200px' }}>
<div className="input-wrap mr10">
<input type="text" className="input-light" readOnly value={form.watch('windSpeed')} {...register('windSpeed')} />
</div>
{/* <div className="select-wrap mr10" style={{ width: '200px' }}>
<select className="select-light" name="windSpeed" {...register('windSpeed')}>
{windSpeedList.map((row) => {
return (
@ -643,15 +676,17 @@ export default function StuffDetail() {
)
})}
</select>
</div>
<span>m/s이하</span>
<button className="btn-origin grey mr5">풍속선택</button>
</div> */}
<span className="mr10">{getMessage('stuff.detail.windSpeedSpan')}</span>
<button className="btn-origin grey" onClick={onSearchWindSpeedPopOpen}>
{getMessage('stuff.detail.btn.windSpeedPop')}
</button>
</div>
</td>
</tr>
<tr>
<th>
수직적설량 <span className="important">*</span>
{getMessage('stuff.detail.verticalSnowCover')} <span className="important">*</span>
</th>
<td>
<div className="flx-box">
@ -665,14 +700,14 @@ export default function StuffDetail() {
<span className="mr10">cm</span>
<div className="d-check-box light">
<input type="checkbox" id="coldRegionFlg" {...form.register('coldRegionFlg')} />
<label htmlFor="coldRegionFlg">한랭지대책시행</label>
<label htmlFor="coldRegionFlg">{getMessage('stuff.detail.coldRegionFlg')}</label>
</div>
</div>
</td>
</tr>
<tr>
<th>
면조도구분 <span className="important">*</span>
{getMessage('stuff.detail.surfaceType')} <span className="important">*</span>
</th>
<td>
<div className="flx-box">
@ -686,7 +721,7 @@ export default function StuffDetail() {
</div>
<div className="d-check-box light mr5">
<input type="checkbox" id="saltAreaFlg" {...form.register('saltAreaFlg')} />
<label htmlFor="saltAreaFlg">염해지역용아이템사용</label>
<label htmlFor="saltAreaFlg">{getMessage('stuff.detail.saltAreaFlg')}</label>
</div>
<div className="tooltips"></div>
</div>
@ -694,7 +729,7 @@ export default function StuffDetail() {
</tr>
<tr>
<th>
설치높이 <span className="important">*</span>
{getMessage('stuff.detail.installHeight')} <span className="important">*</span>
</th>
<td>
<div className="flx-box">
@ -711,25 +746,25 @@ export default function StuffDetail() {
</td>
</tr>
<tr>
<th>계약조건</th>
<th>{getMessage('stuff.detail.conType')}</th>
<td>
<div className="flx-box">
<div className="d-check-radio light mr10">
<input type="radio" name="conType" value="0" id="conType0" {...form.register('conType')} />
<label htmlFor="conType0">잉여</label>
<label htmlFor="conType0">{getMessage('stuff.detail.conType0')}</label>
</div>
<div className="d-check-radio light mr10">
<input type="radio" name="conType" value="1" id="conType1" {...form.register('conType')} />
<label htmlFor="conType1">전량</label>
<label htmlFor="conType1">{getMessage('stuff.detail.conType1')}</label>
</div>
</div>
</td>
</tr>
<tr>
<th>메모</th>
<th>{getMessage('stuff.detail.remarks')}</th>
<td>
<div className="input-wrap">
<input type="text" className="input-light" />
<input type="text" className="input-light" {...form.register('remarks')} />
</div>
</td>
</tr>
@ -756,34 +791,152 @@ export default function StuffDetail() {
</form>
)) || (
<>
<form onSubmit={handleSubmit(onValid)}>
<div className="sub-table-box">
<div className="promise-gudie">
<span className="important">*</span> {getMessage('stuff.detail.required')}
</div>
<div className="infomation-table">
<table>
<colgroup>
<col style={{ width: '200px' }} />
<col />
</colgroup>
<tbody>
<tr>
<th>{getMessage('stuff.detail.planReqNo')}</th>
<td>
<div className="flx-box">
<div className="input-wrap mr5" style={{ width: '200px' }}>
<input type="text" className="input-light" readOnly />
</div>
<button className="btn-origin grey" onClick={onSearchDesignRequestPopOpen}>
{getMessage('stuff.planReqPopup.title')}
</button>
</div>
</td>
</tr>
<tr>
<th>
{getMessage('stuff.detail.dispCompanyName')} <span className="important">*</span>
</th>
<td>
<div className="input-wrap" style={{ width: '500px' }}>
<input type="text" className="input-light" {...form.register('dispCompanyName')} value={form.watch('dispCompanyName')} />
</div>
</td>
</tr>
<tr>
<th>
{getMessage('stuff.detail.objectStatusId')} <span className="importatn">*</span>
</th>
<td>
<div className="flx-box">
<div className="d-check-radio light mr10">
<input type="radio" name="objectStatusId" value="0" id="objectStatus0" {...form.register('objectStatusId')} />
<label htmlFor="objectStatus0">{getMessage('stuff.detail.objectStatus0')}</label>
</div>
<div className="d-check-radio light mr10">
<input type="radio" name="objectStatusId" value="1" id="objectStatus1" {...form.register('objectStatusId')} />
<label htmlFor="objectStatus1">{getMessage('stuff.detail.objectStatus1')}</label>
</div>
<div className="input-wrap mr5" style={{ width: '545px' }}>
<input type="text" className="input-light" {...form.register('objectName')} />
</div>
<div className="select-wrap" style={{ width: '120px' }}>
<select className="select-light" name="objectNameOmit" {...register('objectNameOmit')}>
<option value="">경칭선택</option>
<option value="11">경칭11</option>
<option value="22">경칭22</option>
<option value="33">경칭33</option>
</select>
</div>
</div>
</td>
</tr>
<tr>
<th>{getMessage('stuff.detail.objectNameKana')}</th>
<td>
<div className="input-wrap" style={{ width: '789px' }}>
<input type="text" className="input-light" {...form.register('objectNameKana')} />
</div>
</td>
</tr>
<tr>
<th>
<div className="flx-box">
<div className="title">
{getMessage('stuff.detail.saleStoreId')}
<span className="important">*</span>
</div>
<div className="tooltips"></div>
</div>
</th>
<td>
<div className="flx-box">
<div className="select-wrap mr5" style={{ width: '567px' }}>
{/* <Select
options={saleStoreList}
value={form.watch('saleStoreId')}
onChange={onSelectionChange}
labelField="saleStoreName"
valueField="saleStoreId"
searchBy="saleStoreName"
clearable={true}
></Select> */}
</div>
<div className="input-wrap" style={{ width: '216px' }}>
<input type="text" className="input-light" value={form.watch('saleStoreId')} {...form.register('saleStoreId')} readOnly />
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</form>
{objectNo.substring(0, 1) === 'R' ? (
<>
<Link href="/management/stuff">
<button type="button" className="btn-origin grey">
<button type="button" className="btn-origin grey mr5">
R상세:물건목록
</button>
</Link>
<button type="submit" className="btn-origin navy mr5">
R상세:저장
</button>
<button type="submit" className="btn-origin navy mr5" onClick={onDelete}>
<button type="submit" className="btn-origin navy" onClick={onDelete}>
R상세:물건삭제
</button>
</>
) : (
<>
{!isFormValid ? (
<button type="submit" className="btn-origin navy mr5" onClick={onTempSave}>
TEMP상세:임시저장
</button>
) : (
<button type="submit" className="btn-origin navy mr5">
TEMP상세:저장
</button>
)}
<Link href="/management/stuff">
<button type="button" className="btn-origin grey">
T상세:물건목록
</button>
</Link>
<button type="submit" className="btn-origin navy mr5">
T상세:저장
</button>
</>
)}
</>
)}
{showAddressButtonValid && <FindAddressPop setShowAddressButtonValid={setShowAddressButtonValid} zipInfo={setZipInfo} />}
{showDesignRequestButtonValid && (
<PlanRequestPop setShowDesignRequestButtonValid={setShowDesignRequestButtonValid} planReqInfo={setPlanReqInfo} />
)}
{showWindSpeedButtonValid && (
<WindSelectPop setShowWindSpeedButtonValid={setShowWindSpeedButtonValid} prefName={form.watch('prefName')} windSpeedInfo={setWindSppedInfo} />
)}
</>
)
}

View File

@ -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 (
<div className="ag-theme-quartz" style={{ height: 500 }}>
<AgGridReact
ref={gridRef}
onGridReady={onGridReady}
rowBuffer={rowBuffer}
rowData={rowData}
columnDefs={colDefs}
@ -107,10 +95,9 @@ export default function StuffQGrid(props) {
onSelectionChanged={onSelectionChanged}
onCellDoubleClicked={onCellDoubleClicked}
pagination={isPageable}
//paginationPageSize={paginationPageSize}
//paginationPageSizeSelector={paginationPageSizeSelector}
autoSizeStrategy={autoSizeStrategy}
overlayNoRowsTemplate={'<span className="ag-overlay-loading-center">물건 목록이 없습니다.</span>'}
getRowClass={getRowClass}
autoSizeAllColumns={true}
/>
</div>
)

View File

@ -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() {
<div className="sub-table-box">
<div className="table-box-title-wrap">
<div className="title-wrap">
<h3>물건현황</h3>
<h3>{getMessage('stuff.search.title')}</h3>
</div>
<div className="left-unit-box">
<Link href="/management/stuff/tempdetail">
<Link href="/management/stuff/tempdetail" scroll={false}>
<button type="button" className="btn-origin navy mr5">
물건신규등록
{getMessage('stuff.search.btn1')}
</button>
</Link>
<button type="button" className="btn-origin navy mr5" onClick={onSubmit}>
조회
{getMessage('stuff.search.btn2')}
</button>
<button type="button" className="btn-origin grey" onClick={resetRecoil}>
초기화
{getMessage('stuff.search.btn3')}
</button>
</div>
</div>
@ -175,13 +183,12 @@ export default function StuffSearchCondition() {
</colgroup>
<tbody>
<tr>
<th>물건번호</th>
<th>{getMessage('stuff.search.schObjectNo')}</th>
<td>
<div className="input-wrap">
<input
type="text"
className="input-light"
placeholder="물건번호 입력"
value={stuffSearch?.code === 'E' || stuffSearch?.code === 'M' ? stuffSearch.schObjectNo : objectNo}
onChange={(e) => {
setObjectNo(e.target.value)
@ -190,29 +197,26 @@ export default function StuffSearchCondition() {
/>
</div>
</td>
<th>판매대리점명</th>
<th>{getMessage('stuff.search.schSaleStoreName')}</th>
<td>
<div className="input-wrap">
<input
type="text"
className="input-light"
placeholder="판매대리점명 입력"
value={stuffSearch?.schSaleStoreName ? stuffSearch.schSaleStoreName : saleStoreName}
onChange={(e) => {
//setSaleStoreId(e.target.value)
setSaleStoreName(e.target.value)
setStuffSearch({ ...stuffSearch, code: 'S', schSaleStoreName: e.target.value })
}}
/>
</div>
</td>
<th>물건주소</th>
<th>{getMessage('stuff.search.schAddress')}</th>
<td>
<div className="input-wrap">
<input
type="text"
className="input-light"
placeholder="물건주소 입력"
value={stuffSearch?.schAddress ? stuffSearch.schAddress : address}
onChange={(e) => {
setAddress(e.target.value)
@ -223,13 +227,12 @@ export default function StuffSearchCondition() {
</td>
</tr>
<tr>
<th>물건명</th>
<th>{getMessage('stuff.search.schObjectName')}</th>
<td>
<div className="input-wrap">
<input
type="text"
className="input-light"
placeholder="물건명 입력"
value={stuffSearch?.schObjectName ? stuffSearch.schObjectName : objectName}
onChange={(e) => {
setobjectName(e.target.value)
@ -238,13 +241,12 @@ export default function StuffSearchCondition() {
/>
</div>
</td>
<th>견적처</th>
<th>{getMessage('stuff.search.schDispCompanyName')}</th>
<td>
<div className="input-wrap">
<input
type="text"
className="input-light"
placeholder="견적처 입력"
value={stuffSearch?.schDispCompanyName ? stuffSearch.schDispCompanyName : dispCompanyName}
onChange={(e) => {
setDispCompanyName(e.target.value)
@ -253,31 +255,34 @@ export default function StuffSearchCondition() {
/>
</div>
</td>
<th>판매대리점 선택</th>
<th>{getMessage('stuff.search.schSelSaleStoreId')}</th>
<td>
{schSelSaleStoreList?.length > 0 && (
<Select
options={schSelSaleStoreList}
value={stuffSearch?.schSelSaleStoreId ? stuffSearch.schSelSaleStoreId : schSelSaleStoreId}
labelField="saleStoreName"
valueField="saleStoreName"
onChange={onSelectionChange}
clearable={true}
onClearAll={handleClear}
ref={ref}
disabled={sessionState?.storeLvl === '1' ? false : true}
></Select>
)}
<div className="select-wrap">
{schSelSaleStoreList?.length > 0 && (
<Select
className="react-select-custom"
classNamePrefix="custom"
placeholder="Select"
ref={ref}
options={schSelSaleStoreList}
onChange={onSelectionChange}
getOptionLabel={(x) => x.saleStoreName}
getOptionValue={(x) => x.saleStoreId}
defaultValue={stuffSearch?.selObject?.value ? stuffSearch?.selObject : null}
isDisabled={sessionState?.storeLvl === '1' ? false : true}
isClearable={true}
/>
)}
</div>
</td>
</tr>
<tr>
<th>담당자</th>
<th>{getMessage('stuff.search.schReceiveUser')}</th>
<td>
<div className="input-wrap">
<input
type="text"
className="input-light"
placeholder="담당자 입력"
value={stuffSearch?.schReceiveUser ? stuffSearch.schReceiveUser : receiveUser}
onChange={(e) => {
setReceiveUser(e.target.value)
@ -286,7 +291,7 @@ export default function StuffSearchCondition() {
/>
</div>
</td>
<th>기간검색</th>
<th>{getMessage('stuff.search.period')}</th>
<td colSpan={3}>
<div className="form-flex-wrap">
<div className="radio-wrap mr10">
@ -302,7 +307,7 @@ export default function StuffSearchCondition() {
setStuffSearch({ ...stuffSearch, code: 'S', schDateType: e.target.value })
}}
/>
<label htmlFor="radio_u">갱신일</label>
<label htmlFor="radio_u">{getMessage('stuff.search.schDateTypeU')}</label>
</div>
<div className="d-check-radio light">
<input
@ -316,7 +321,7 @@ export default function StuffSearchCondition() {
setStuffSearch({ ...stuffSearch, code: 'S', schDateType: e.target.value })
}}
/>
<label htmlFor="radio_r">등록일</label>
<label htmlFor="radio_r">{getMessage('stuff.search.schDateTypeR')}</label>
</div>
</div>
<div className="date-picker-wrap">

View File

@ -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 (
<div className="modal-popup">
<div className="modal-dialog middle">
<div className="modal-content">
<div className="modal-header">
<h1 className="title">{getMessage('stuff.addressPopup.title')}</h1>
<button className="modal-close" onClick={() => props.setShowAddressButtonValid(false)}>
닫기
</button>
</div>
<div className="modal-body">
<div className="modal-body-inner">
<div className="address-input-wrap">
<input
type="text"
className="address-input"
maxLength={7}
onKeyUp={handleKeyUp}
{...form.register('zipNo')}
placeholder={getMessage('stuff.addressPopup.placeholder')}
/>
<button className="search-btn" onClick={searchPostNum}></button>
</div>
<div className="address-grid">
<FindAddressPopQGrid {...gridProps} getSelectedRowdata={getSelectedRowdata} />
</div>
</div>
<div className="footer-btn-wrap">
<button className="btn-origin grey mr5" onClick={() => props.setShowAddressButtonValid(false)}>
{getMessage('stuff.addressPopup.btn1')}
</button>
<button className="btn-origin navy " onClick={applyAddress}>
{getMessage('stuff.addressPopup.btn2')}
</button>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@ -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 (
<div className="ag-theme-quartz" style={{ height: 500 }}>
<AgGridReact
onGridReady={onGridReady}
rowBuffer={rowBuffer}
rowData={rowData}
columnDefs={colDefs}
defaultColDef={defaultColDef}
rowSelection={'singleRow'}
pagination={isPageable}
onSelectionChanged={onSelectionChanged}
/>
</div>
)
}

View File

@ -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 (
<div className="modal-popup">
<div className="modal-dialog big">
<div className="modal-content">
<div className="modal-header">
<h1 className="title">{getMessage('stuff.planReqPopup.title')}</h1>
<button className="modal-close" onClick={() => props.setShowDesignRequestButtonValid(false)}>
닫기
</button>
</div>
<div className="modal-body">
<div className="modal-body-inner">
<div className="design-tit-wrap">
<h3>{getMessage('stuff.planReqPopup.popTitle')}</h3>
<div className="design-search-wrap">
<button
className="btn-origin navy mr5"
onClick={() => {
onSubmit(pageNo, 'S')
}}
>
{getMessage('stuff.planReqPopup.btn1')}
</button>
<button className="btn-origin grey" onClick={resetRecoil}>
{getMessage('stuff.planReqPopup.btn2')}
</button>
</div>
</div>
<div className="design-request-table">
<div className="common-table">
<table>
<colgroup>
<col style={{ width: '160px' }} />
<col />
<col style={{ width: '160px' }} />
<col />
<col style={{ width: '160px' }} />
<col />
</colgroup>
<tbody>
<tr>
<th>{getMessage('stuff.planReqPopup.search.planReqNo')}</th>
<td>
<div className="input-wrap">
<input
type="text"
className="input-light"
value={planReqSearch?.schPlanReqNo ? planReqSearch?.schPlanReqNo : schPlanReqNo}
onChange={(e) => {
setSchPlanReqNo(e.target.value)
setPlanReqSearch({ ...planReqSearch, schPlanReqNo: e.target.value })
}}
/>
</div>
</td>
<th>{getMessage('stuff.planReqPopup.search.title')}</th>
<td>
<div className="input-wrap">
<input
type="text"
className="input-light"
value={planReqSearch?.schTitle ? planReqSearch?.schTitle : schTitle}
onChange={(e) => {
setSchTitle(e.target.value)
setPlanReqSearch({ ...planReqSearch, schTitle: e.target.value })
}}
/>
</div>
</td>
<th>{getMessage('stuff.planReqPopup.search.address1')}</th>
<td>
<div className="input-wrap">
<input
type="text"
className="input-light"
value={planReqSearch?.schAddress ? planReqSearch?.schAddress : schAddress}
onChange={(e) => {
setSchAddress(e.target.value)
setPlanReqSearch({ ...planReqSearch, schAddress: e.target.value })
}}
/>
</div>
</td>
</tr>
<tr>
<th>{getMessage('stuff.planReqPopup.search.saleStoreName')}</th>
<td>
<div className="input-wrap">
<input
type="text"
className="input-light"
value={planReqSearch?.schSaleStoreName ? planReqSearch?.schSaleStoreName : schSaleStoreName}
onChange={(e) => {
setSchSaleStoreName(e.target.value)
setPlanReqSearch({ ...planReqSearch, schSaleStoreName: e.target.value })
}}
/>
</div>
</td>
<th>{getMessage('stuff.planReqPopup.search.planReqName')}</th>
<td>
<div className="input-wrap">
<input
type="text"
className="input-light"
value={planReqSearch?.schPlanReqName ? planReqSearch?.schPlanReqName : schPlanReqName}
onChange={(e) => {
setSchPlanReqName(e.target.value)
setPlanReqSearch({ ...planReqSearch, schPlanReqName: e.target.value })
}}
/>
</div>
</td>
<th>{getMessage('stuff.planReqPopup.search.planStatName')}</th>
<td>
<div className="select-wrap">
<Select
className="react-select-custom"
classNamePrefix="custom"
ref={ref}
options={tempList}
onChange={onSelectionChange}
isSearchable={false}
placeholder="Select"
isClearable={true}
/>
</div>
</td>
</tr>
<tr>
<th>{getMessage('stuff.planReqPopup.search.period')}</th>
<td colSpan={5}>
<div className="form-flex-wrap">
<div className="radio-wrap mr10">
<div className="d-check-radio light mr10">
<input
type="radio"
name="radio04"
id="ra07"
checked={planReqSearch?.schDateGbn === 'S' ? true : false}
value={'S'}
onChange={(e) => {
setSchDateGbn(e.target.value)
setPlanReqSearch({ ...planReqSearch, schDateGbn: e.target.value })
}}
/>
<label htmlFor="ra07">{getMessage('stuff.planReqPopup.search.schDateGbnS')}</label>
</div>
<div className="d-check-radio light">
<input
type="radio"
name="radio04"
id="ra08"
checked={planReqSearch?.schDateGbn === 'R' ? true : false}
value={'R'}
onChange={(e) => {
setSchDateGbn(e.target.value)
setPlanReqSearch({ ...planReqSearch, schDateGbn: e.target.value })
}}
/>
<label htmlFor="ra08">{getMessage('stuff.planReqPopup.search.schDateGbnR')}</label>
</div>
</div>
<div className="date-picker-wrap">
<div className="date-picker" style={{ flex: 1 }}>
<SingleDatePicker {...rangeDatePickerProps1} />
</div>
<span> ~ </span>
<div className="date-picker" style={{ flex: 1 }}>
<SingleDatePicker {...rangeDatePickerProps2} />
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div className="design-request-grid">
<div className="design-request-grid-tit">Plan List</div>
<PlanRequestPopQGrid {...gridProps} getSelectedRowdata={getSelectedRowdata} />
<div className="pagination-wrap">
<QPagination pageNo={pageNo} pageSize={pageSize} pagePerBlock={10} totalCount={totalCount} handleChangePage={handleChangePage} />
</div>
</div>
</div>
<div className="footer-btn-wrap">
<button className="btn-origin grey mr5" onClick={() => props.setShowDesignRequestButtonValid(false)}>
{getMessage('stuff.planReqPopup.btn3')}
</button>
<button className="btn-origin navy" onClick={applyPlanReq}>
{getMessage('stuff.planReqPopup.btn4')}
</button>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@ -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 (
<div className="ag-theme-quartz" style={{ height: 350 }}>
<AgGridReact
onGridReady={onGridReady}
rowBuffer={rowBuffer}
rowData={rowData}
columnDefs={colDefs}
defaultColDef={defaultColDef}
rowSelection={'singleRow'}
pagination={isPageable}
onSelectionChanged={onSelectionChanged}
/>
</div>
)
}

View File

@ -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 (
<div className="modal-popup">
<div className="modal-dialog middle">
<div className="modal-content">
<div className="modal-header">
<h1 className="title">{getMessage('stuff.windSelectPopup.title')}</h1>
<button className="modal-close" onClick={() => props.setShowWindSpeedButtonValid(false)}>
닫기
</button>
</div>
<div className="modal-body">
<div className="modal-body-inner">
<div className="common-table">
<table>
<colgroup>
<col style={{ width: '70px' }} />
<col />
</colgroup>
<tbody>
<tr>
<th>{getMessage('stuff.windSelectPopup.search.address1')}</th>
<td>
<div className="input-wrap">
<input type="text" className="input-light" defaultValue={props.prefName} readOnly />
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div className="wind-table">
<table>
<thead>
<tr>
<th style={{ width: '50px' }}>{getMessage('stuff.windSelectPopup.table.selected')}</th>
<th style={{ width: '110px' }}>{getMessage('stuff.windSelectPopup.table.windspeed')}</th>
<th>Remarks</th>
</tr>
</thead>
<tbody>
{windSpeedList.map((row, index) => {
// console.log('row:::', row)
return (
<tr key={index}>
<td className="al-c">
<div className="d-check-radio light no-text">
<input type="radio" value={row.windSpeed} name="windS" id={row.windSpeed} onChange={handleChangeRadio} />
<label htmlFor={row.windSpeed}></label>
</div>
</td>
<td className="al-c">{row.windSpeed}</td>
<td>{row.remarks}</td>
</tr>
)
})}
</tbody>
</table>
</div>
</div>
<div className="footer-btn-wrap">
<button className="btn-origin grey mr5" onClick={() => props.setShowWindSpeedButtonValid(false)}>
{getMessage('stuff.windSelectPopup.btn1')}
</button>
<button className="btn-origin navy" onClick={applyWindSpeed}>
{getMessage('stuff.windSelectPopup.btn2')}
</button>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@ -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 (
<>
<div className="modal-popup">
<div className="modal-dialog ">
<div className="modal-content">
<div className="modal-header">
<h1 className="title">{getMessage('myinfo.title')}</h1>
<button
type="button"
className="modal-close"
onClick={() => {
setUserInfoModal(false)
}}
>
{getMessage('myinfo.btn.close')}
</button>
</div>
<div className="modal-body">
<div className="modal-body-inner">
<div className="common-table">
<table>
<colgroup>
<col style={{ width: '170px' }} />
<col />
</colgroup>
<tbody>
<tr>
<th>{getMessage('myinfo.info.userId')}</th>
<td>
<div className="input-wrap">
<input type="text" className="input-light" value={userId} readOnly />
</div>
</td>
</tr>
<tr>
<th>{getMessage('myinfo.info.nameKana')}</th>
<td>
<div className="input-wrap">
<input type="text" className="input-light" value={info?.nameKana} readOnly />
</div>
</td>
</tr>
<tr>
<th>{getMessage('myinfo.info.name')}</th>
<td>
<div className="input-wrap">
<input type="text" className="input-light" value={info?.name} readOnly />
</div>
</td>
</tr>
<tr>
<th>{getMessage('myinfo.info.password')}</th>
<td>
<div className="form-flex-wrap">
<div className="password-input mr10">
<input
type={`${showPwd ? 'text' : 'password'}`}
value={password}
ref={pwdInput}
onChange={(e) => {
setPassword(e.target.value)
}}
/>
<button className={`blink ${showPwd ? 'on' : ''}`} onClick={() => setShowPwd(!showPwd)}></button>
</div>
<span className="mr10">{getMessage('myinfo.sub.validation.password')}</span>
<button
className="btn-origin grey mr5"
type="button"
onClick={(e) => {
checkPasswordProcess(e)
}}
>
{getMessage('myinfo.btn.chg.password')}
</button>
</div>
</td>
</tr>
<tr style={{ display: chkChgPwd ? '' : 'none' }}>
<th>
{getMessage('myinfo.info.chg.password')} <span className="red">*</span>
</th>
<td>
<div className="form-flex-wrap">
<div className="input-wrap mr10" style={{ flex: 1 }}>
<input
className="input-light"
type="text"
ref={chgPwdInput}
value={chgPwd}
onChange={(e) => {
setChgPwd(e.target.value)
}}
/>
</div>
<button type="button" className="btn-origin grey mr5" onClick={handleChangePassword}>
{getMessage('myinfo.btn.chg')}
</button>
<button
type="button"
className="btn-origin white "
onClick={() => {
setChgPwd('')
setChkChgPwd(false)
}}
>
{getMessage('myinfo.btn.noChg')}
</button>
</div>
</td>
</tr>
<tr>
<th>{getMessage('myinfo.info.category')}</th>
<td>
<div className="input-wrap">
<input type="text" className="input-light" value={info?.groupName} readOnly />
</div>
</td>
</tr>
<tr>
<th>{getMessage('myinfo.info.tel')}</th>
<td>
<div className="input-wrap">
<input type="text" className="input-light" value={info?.tel} readOnly />
</div>
</td>
</tr>
<tr>
<th>{getMessage('myinfo.info.fax')}</th>
<td>
<div className="input-wrap">
<input type="text" className="input-light" value={info?.fax} readOnly />
</div>
</td>
</tr>
<tr>
<th>{getMessage('myinfo.info.mail')}</th>
<td>
<div className="input-wrap">
<input type="text" className="input-light" value={info?.mail} readOnly />
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div className="footer-btn-wrap">
<button
type="button"
className="btn-origin navy "
onClick={() => {
setUserInfoModal(false)
}}
>
{getMessage('myinfo.btn.confirm')}
</button>
</div>
</div>
</div>
</div>
</div>
</>
)
}

View File

@ -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,
}
}

View File

@ -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 {

View File

@ -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) => {

View File

@ -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 {

View File

@ -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 }

View File

@ -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,
}
}

View File

@ -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 }
}

View File

@ -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
}

View File

@ -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,
}
}

View File

@ -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,
}
}

View File

@ -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,
}
}

View File

@ -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 }
}

View File

@ -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) => {

160
src/hooks/useContextMenu.js Normal file
View File

@ -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: <AuxiliarySize setCurrentContextMenu={setCurrentContextMenu} />,
},
{
id: 'auxiliaryMove',
name: '보조선 이동(M)',
component: <AuxiliaryMove setCurrentContextMenu={setCurrentContextMenu} />,
},
{
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,
}
}

Some files were not shown because too many files have changed in this diff Show More