Compare commits

...

56 Commits

Author SHA1 Message Date
a2d192084b usePlan 내부 currentObject 초기화 제거 2025-03-26 10:09:40 +09:00
cdaeab1d42 Merge branch 'dev' into feature/dev-yj 2025-03-25 16:07:54 +09:00
47e3ae7d29 배치면 타입 상하 반전 추가 2025-03-25 16:07:15 +09:00
65ec3d5153 지붕면 할당 시 오류 수정 2025-03-25 15:12:01 +09:00
yjnoh
592275c0de 배치면 도형 재배치 및 처마,용마루,케라바 타입 변경 로직 변경 2025-03-25 14:49:07 +09:00
5e27ab282c Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-03-25 11:11:15 +09:00
75549b66b5 지붕재 변경 시 canvas 저장 추가 2025-03-25 11:10:56 +09:00
yjnoh
437d552b3a 배치면 방향 수정 2025-03-25 11:05:54 +09:00
85d9aca6d3 병렬수 0 이상인 경우에만 추가 2025-03-24 14:50:06 +09:00
yjnoh
7ec9854173 Merge branch 'dev' into feature/dev-yj 2025-03-24 11:26:42 +09:00
yjnoh
b458b4e853 캔버스 확시대 오브젝트 클릭 수정 2025-03-24 11:26:26 +09:00
1ba9853a32 #935 전반/글자표기 2025-03-24 11:06:35 +09:00
f1d976521d # 932 - pcs 한국어 기재 수정 2025-03-24 10:55:53 +09:00
a7ddfacdd2 Merge pull request '견적서 복사 미사용 파라미터 정리' (#7) from feature/qcast-925 into dev
Reviewed-on: #7
2025-03-24 09:28:27 +09:00
b51dacf421 견적서 복사 미사용 파라미터 정리 2025-03-24 09:27:28 +09:00
f25dac0ae3 Merge pull request '견적특이사항 상품 pkgYn 대응' (#6) from feature/qcast-925 into dev
Reviewed-on: #6
2025-03-21 17:48:24 +09:00
c99deaf93f 견적특이사항 상품 pkgYn 대응 2025-03-21 17:47:41 +09:00
8d65765daf Merge pull request '견적특이사항 공통코드 PROD 사용유무 변경에 따른 대응..' (#5) from feature/qcast-925 into dev
Reviewed-on: #5
2025-03-21 16:04:37 +09:00
2f8ca712c9 견적특이사항 공통코드 PROD 사용유무 변경에 따른 대응.. 2025-03-21 16:03:48 +09:00
f2470b346c feat: 핫키 이벤트 등록시 구문 수정 2025-03-21 14:23:46 +09:00
1a5f78a970 Merge pull request '견적서 복사 실패 메세지' (#4) from feature/qcast-925 into dev
Reviewed-on: #4
2025-03-21 13:27:21 +09:00
5979555bcb 견적서 복사 실패 메세지 2025-03-21 13:26:14 +09:00
26047df3c8 feat: 핫키 이벤트 등록 샘플 추가 2025-03-21 11:28:16 +09:00
yjnoh
12936ec1f9 Merge branch 'dev' into feature/dev-yj 2025-03-21 11:24:08 +09:00
yjnoh
1bb92a975e 마우스 우클릭 이벤트 조정 2025-03-21 11:23:58 +09:00
f2a083f022 지붕재 할당 수정 추가 2025-03-21 10:23:46 +09:00
786c35e656 intersection 검색 범위 수정 2025-03-21 09:57:51 +09:00
yjnoh
88bcf27bfb 수정 2025-03-20 18:09:40 +09:00
yjnoh
617afb8b1f Merge branch 'dev' into feature/dev-yj-surface 2025-03-20 18:00:30 +09:00
yjnoh
414d6fa0c5 처마 용마루 방향에 따른 타입 작업 2025-03-20 17:59:58 +09:00
6919dac8f1 getCurrentPoints로 수정 2025-03-20 16:48:35 +09:00
yjnoh
79d873c135 진짜 처마면인지 확인하는 로직 추가 2025-03-20 16:00:02 +09:00
yjnoh
0e8ce8b2e2 Merge branch 'dev' into feature/dev-yj-surface 2025-03-20 15:30:47 +09:00
yjnoh
c4d17d2147 배치면 라인 타입 팝업 삭제 및 로직 수정 2025-03-20 15:30:03 +09:00
e75db5ace1 Merge remote-tracking branch 'origin/dev' into dev 2025-03-20 14:18:26 +09:00
8848713c72 Merge pull request '견적서 일본어 변경 누락분 재반영' (#3) from feature/qcast-925 into dev
Reviewed-on: #3
2025-03-20 14:17:30 +09:00
39d48e61f3 test: push test 2025-03-20 13:48:07 +09:00
e624aa0de0 메시지 내용 수정 2025-03-20 13:42:07 +09:00
e7410e5373 견적서 일본어 변경 누락분 재반영 2025-03-20 13:35:06 +09:00
05dd069e53 modified 시 lines 위치 새로 수정 2025-03-20 10:02:09 +09:00
ac015123cd Merge pull request '#925 견적서 상세 가격표시 옵션 & 프라이싱 호출 파라미터 변경' (#2) from feature/qcast-925 into dev
Reviewed-on: #2
2025-03-19 17:40:55 +09:00
f7fe0f6528 #925 견적서 상세 가격표시 옵션 & 프라이싱 호출 파라미터 변경 2025-03-19 17:40:13 +09:00
6b76108d8b Merge pull request '자동로그인 id 입력 수정 #913' (#1) from feature/qcast-913 into dev
Reviewed-on: #1
2025-03-19 16:23:45 +09:00
BOOK-BKT8UBVE0A\dhfkd
590040fa1d 자동로그인 id 입력 수정 #913 2025-03-19 16:23:19 +09:00
yjnoh
325c2c1cc0 번역수정 2025-03-19 13:28:20 +09:00
yjnoh
521bfd4303 반각만 입력가능 문구 추가 2025-03-19 11:10:12 +09:00
84e8af50b8 QSelectbox key 중복 제거 2025-03-18 15:55:11 +09:00
57446fa6d8 주택 원복 2025-03-18 12:54:36 +09:00
1fa02de62f 지붕면 할당 순서 수정 2025-03-18 11:33:45 +09:00
06fa1766d6 파라미터 없는 경우 같은 포인트 검사 못함 2025-03-18 10:56:37 +09:00
63297328ed
Merge Q-CAST-III-MR-783: YJSS 금액 2025-03-18 01:30:08 +00:00
7c15da2b4c YJSS 금액 2025-03-18 10:29:26 +09:00
3432d64a3c 8각 안나눠지는 현상 작업 추가 2025-03-18 10:23:42 +09:00
9be21fc2b2 번역 수정 #914 2025-03-18 09:52:14 +09:00
yjnoh
c467fc7fa8 흡착점 작업 2025-03-18 09:36:11 +09:00
yoosangwook
f915dab239 chore: ecosystem 추가 2025-03-17 18:25:28 +09:00
36 changed files with 806 additions and 325 deletions

10
ecosystem.config.js Normal file
View File

@ -0,0 +1,10 @@
module.exports = {
apps: [
{
name: 'qcast-front-production',
script: 'npm run start:dev',
instance: 2,
exec_mode: 'cluster',
},
],
}

View File

@ -5,7 +5,8 @@
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start -p 5000", "start:cluster1": "next start -p 5000",
"start:cluster2": "next start -p 5001",
"start:dev": "next start -p 5010", "start:dev": "next start -p 5010",
"lint": "next lint", "lint": "next lint",
"serve": "node server.js" "serve": "node server.js"

View File

@ -203,6 +203,7 @@ export const SAVE_KEY = [
'fontWeight', 'fontWeight',
'dormerAttributes', 'dormerAttributes',
'toFixed', 'toFixed',
'isSortedPoints',
] ]
export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype, fabric.Group.prototype] export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype, fabric.Group.prototype]

View File

@ -2,26 +2,111 @@
import { useState } from 'react' import { useState } from 'react'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import { setSession, login } from '@/lib/authActions'
import { sessionStore } from '@/store/commonAtom'
import { useRecoilState } from 'recoil'
import { useAxios } from '@/hooks/useAxios'
import { globalLocaleStore } from '@/store/localeAtom'
import { useRouter } from 'next/navigation'
import GlobalSpinner from '@/components/common/spinner/GlobalSpinner' import GlobalSpinner from '@/components/common/spinner/GlobalSpinner'
export default function AutoLoginPage() { export default function AutoLoginPage({ autoLoginParam }) {
const [isLoading, setIsLoading] = useState(true) const router = useRouter()
const [isLoading, setIsLoading] = useState(autoLoginParam === 'Y' ? false : true)
const [globalLocaleState, setGlbalLocaleState] = useRecoilState(globalLocaleStore)
const { promisePost } = useAxios(globalLocaleState)
const { getMessage } = useMessage() const { getMessage } = useMessage()
const [userId, setUserId] = useState('')
const [sessionState, setSessionState] = useRecoilState(sessionStore)
const [idFocus, setIdFocus] = useState(false)
const loginProcess = async () => {
setIsLoading(true)
await promisePost({ url: '/api/login/v1.0/user', data: { loginId: userId } }).then((response) => {
setIsLoading(false)
if (response.data) {
const res = response.data
const result = { ...res, storeLvl: res.groupId === '60000' ? '1' : '2', pwdInitYn: 'Y' }
setSession(result)
setSessionState(result)
login()
} else {
alert(getMessage('login.fail'))
router.push('/login?autoLoginParam1=Y')
}
})
}
return ( return (
<> <>
{isLoading && <GlobalSpinner />} {isLoading && <GlobalSpinner />}
<div className="login-input-frame"> {autoLoginParam !== 'Y' ? (
<div className="login-frame-tit "> <>
<span>{getMessage('site.name')}</span> <div className="login-input-frame">
{getMessage('site.sub_name')} <div className="login-frame-tit ">
</div> <span>{getMessage('site.name')}</span>
<div className="login-input-wrap"> {getMessage('site.sub_name')}
<div className="login-area id" style={{ fontWeight: 'bolder' }}> </div>
{getMessage('login.auto.page.text')} <div className="login-input-wrap">
<div className="login-area id" style={{ fontWeight: 'bolder' }}>
{getMessage('login.auto.page.text')}
</div>
</div>
</div> </div>
</div> </>
</div> ) : (
<>
<div className="login-input-frame">
<form
onSubmit={(e) => {
e.preventDefault()
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 ${idFocus ? 'focus' : ''}`}>
<input
type="text"
className="login-input"
id="userId"
name="id"
required
value={userId}
placeholder={getMessage('login.id.placeholder')}
onChange={(e) => {
setUserId(e.target.value)
}}
onFocus={() => setIdFocus(true)}
onBlur={() => setIdFocus(false)}
/>
<button
type="button"
className="id-delete"
onClick={(e) => {
setUserId('')
}}
></button>
</div>
<div className="login-btn-box">
<button type="submit" className="login-btn">
{getMessage('login')}
</button>
</div>
</div>
</form>
</div>
</>
)}
</> </>
) )
} }

View File

@ -25,7 +25,9 @@ export default function Login() {
useEffect(() => { useEffect(() => {
if (autoLoginParam) { if (autoLoginParam) {
autoLoginProcess(autoLoginParam) if (autoLoginParam !== 'Y') {
autoLoginProcess(autoLoginParam)
}
} }
// console.log('🚀 ~ checkSession ~ checkSession():', checkSession()) // console.log('🚀 ~ checkSession ~ checkSession():', checkSession())
@ -334,7 +336,7 @@ export default function Login() {
</div> </div>
</> </>
)} )}
{autoLoginParam && <AutoLogin />} {autoLoginParam && <AutoLogin autoLoginParam={autoLoginParam} />}
</div> </div>
<div className="login-copyright">COPYRIGHT©2024 Hanwha Japan All Rights Reserved.</div> <div className="login-copyright">COPYRIGHT©2024 Hanwha Japan All Rights Reserved.</div>
</div> </div>

View File

@ -6,29 +6,19 @@ import { contextMenuListState, contextMenuState } from '@/store/contextMenu'
import { useTempGrid } from '@/hooks/useTempGrid' import { useTempGrid } from '@/hooks/useTempGrid'
import { useContextMenu } from '@/hooks/useContextMenu' import { useContextMenu } from '@/hooks/useContextMenu'
import { useEvent } from '@/hooks/useEvent' import { useEvent } from '@/hooks/useEvent'
import { canvasState } from '@/store/canvasAtom' import { canvasState, currentObjectState } from '@/store/canvasAtom'
export default function QContextMenu(props) { export default function QContextMenu(props) {
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
const { contextRef, canvasProps } = props const { contextRef, canvasProps } = props
const [contextMenu, setContextMenu] = useRecoilState(contextMenuState) const [contextMenu, setContextMenu] = useRecoilState(contextMenuState)
const contextMenuList = useRecoilValue(contextMenuListState) const contextMenuList = useRecoilValue(contextMenuListState)
const activeObject = canvasProps?.getActiveObject() // const currentObject = useRecoilValue(currentObjectState)
const { tempGridMode, setTempGridMode } = useTempGrid() const { tempGridMode, setTempGridMode } = useTempGrid()
const { handleKeyup } = useContextMenu() const { handleKeyup } = useContextMenu()
const { addDocumentEventListener, removeDocumentEvent } = useEvent() const { addDocumentEventListener, removeDocumentEvent } = useEvent()
// const { addDocumentEventListener, removeDocumentEvent } = useContext(EventContext) // const { addDocumentEventListener, removeDocumentEvent } = useContext(EventContext)
let contextType = ''
if (activeObject) {
if (activeObject.initOptions && activeObject.initOptions.name) {
//
if (activeObject.initOptions?.name?.indexOf('guide') > -1) {
contextType = 'surface' //
}
}
}
const getYPosition = (e) => { const getYPosition = (e) => {
const contextLength = contextMenuList.reduce((acc, cur, index) => { const contextLength = contextMenuList.reduce((acc, cur, index) => {
return acc + cur.length return acc + cur.length
@ -36,11 +26,13 @@ export default function QContextMenu(props) {
return e?.clientY - (contextLength * 25 + contextMenuList.length * 2 * 17) return e?.clientY - (contextLength * 25 + contextMenuList.length * 2 * 17)
} }
useEffect(() => { const handleContextMenu = (e) => {
if (!contextRef.current) return // e.preventDefault() // contextmenu
if (currentObject) {
const isArray = currentObject.hasOwnProperty('arrayData')
if (isArray && currentObject.arrayData.length === 0) return
const handleContextMenu = (e) => {
e.preventDefault() // contextmenu
if (tempGridMode) return if (tempGridMode) return
const position = { const position = {
x: window.innerWidth / 2 < e.pageX ? e.pageX - 240 : e.pageX, x: window.innerWidth / 2 < e.pageX ? e.pageX - 240 : e.pageX,
@ -48,21 +40,24 @@ export default function QContextMenu(props) {
} }
setContextMenu({ visible: true, ...position, currentMousePos: canvasProps.getPointer(e) }) setContextMenu({ visible: true, ...position, currentMousePos: canvasProps.getPointer(e) })
addDocumentEventListener('keyup', document, handleKeyup) addDocumentEventListener('keyup', document, handleKeyup)
canvasProps?.upperCanvasEl.removeEventListener('contextmenu', handleContextMenu) //
} }
}
const handleClick = (e) => { const handleClick = (e) => {
// e.preventDefault() // e.preventDefault()
setContextMenu({ ...contextMenu, visible: false })
}
const handleOutsideClick = (e) => {
// e.preventDefault()
if (contextMenu.visible) {
setContextMenu({ ...contextMenu, visible: false }) setContextMenu({ ...contextMenu, visible: false })
removeDocumentEvent('keyup')
} }
}
const handleOutsideClick = (e) => { useEffect(() => {
// e.preventDefault() if (!contextRef.current) return
if (contextMenu.visible) {
setContextMenu({ ...contextMenu, visible: false })
removeDocumentEvent('keyup')
}
}
canvasProps?.upperCanvasEl.addEventListener('contextmenu', handleContextMenu) canvasProps?.upperCanvasEl.addEventListener('contextmenu', handleContextMenu)
document.addEventListener('click', handleClick) document.addEventListener('click', handleClick)
@ -72,43 +67,9 @@ export default function QContextMenu(props) {
removeDocumentEvent('keyup') removeDocumentEvent('keyup')
document.removeEventListener('click', handleClick) document.removeEventListener('click', handleClick)
document.removeEventListener('click', handleOutsideClick) document.removeEventListener('click', handleOutsideClick)
canvasProps?.upperCanvasEl.removeEventListener('contextmenu', handleContextMenu) //
} }
}, [contextRef, contextMenuList]) }, [contextRef, contextMenuList, currentObject])
const handleObjectMove = () => {
activeObject.set({
lockMovementX: false, // X
lockMovementY: false, // Y
})
canvasProps?.on('object:modified', function (e) {
activeObject.set({
lockMovementX: true, // X
lockMovementY: true, // Y
})
})
}
const handleObjectDelete = () => {
if (confirm('삭제하실거?')) {
canvasProps.remove(activeObject)
}
}
const handleObjectCopy = () => {
activeObject.clone((cloned) => {
cloned.set({
left: activeObject.left + activeObject.width + 20,
initOptions: { ...activeObject.initOptions },
lockMovementX: true, // X
lockMovementY: true, // Y
lockRotation: true, //
lockScalingX: true, // X
lockScalingY: true, // Y
})
canvasProps?.add(cloned)
})
}
return ( return (
<> <>

View File

@ -87,7 +87,7 @@ export default function QSelectBox({
<ul className="select-item-wrap"> <ul className="select-item-wrap">
{options?.length > 0 && {options?.length > 0 &&
options?.map((option, index) => ( options?.map((option, index) => (
<li key={option.id || index} className="select-item" onClick={() => handleClickSelectOption(option)}> <li key={option.id + '_' + index} className="select-item" onClick={() => handleClickSelectOption(option)}>
<button key={option.id + 'btn'}>{showKey !== '' ? option[showKey] : option.name}</button> <button key={option.id + 'btn'}>{showKey !== '' ? option[showKey] : option.name}</button>
</li> </li>
))} ))}

View File

@ -175,7 +175,10 @@ export default function Estimate({}) {
row.check = false row.check = false
estimateOption.map((row2) => { estimateOption.map((row2) => {
if (row.pkgYn === '0') { if (row.pkgYn === '0') {
if (row2 === row.code) { // if (row2 === row.code) {
// row.check = true
// }
if (row.code.split('、').includes(row2)) {
row.check = true row.check = true
} }
} else { } else {
@ -217,7 +220,10 @@ export default function Estimate({}) {
row.check = false row.check = false
estimateOption.map((row2) => { estimateOption.map((row2) => {
if (row.pkgYn === '0') { if (row.pkgYn === '0') {
if (row2 === row.code) { // if (row2 === row.code) {
// row.check = true
// }
if (row.code.split('、').includes(row2)) {
row.check = true row.check = true
} }
} else { } else {
@ -240,7 +246,6 @@ export default function Estimate({}) {
} }
} }
}) })
setSpecialNoteList(res) setSpecialNoteList(res)
setSpecialNoteFirstFlg(true) setSpecialNoteFirstFlg(true)
@ -377,8 +382,8 @@ export default function Estimate({}) {
useEffect(() => { useEffect(() => {
if (estimateContextState.estimateType !== '') { if (estimateContextState.estimateType !== '') {
const param = { const param = {
saleStoreId: session.storeId, saleStoreId: estimateContextState.sapSaleStoreId,
sapSalesStoreCd: session.custCd, sapSalesStoreCd: estimateContextState.sapSalesStoreCd,
docTpCd: estimateContextState?.estimateType, docTpCd: estimateContextState?.estimateType,
} }
@ -387,6 +392,8 @@ export default function Estimate({}) {
if (isNotEmptyArray(res?.data)) { if (isNotEmptyArray(res?.data)) {
setStorePriceList(res.data) setStorePriceList(res.data)
} }
setItemChangeYn(true)
}) })
if (estimateContextState.estimateType === 'YJSS') { if (estimateContextState.estimateType === 'YJSS') {
@ -416,8 +423,6 @@ export default function Estimate({}) {
handlePricing('UNIT_PRICE') handlePricing('UNIT_PRICE')
} }
} }
setItemChangeYn(true)
} }
}, [estimateContextState?.estimateType]) }, [estimateContextState?.estimateType])
@ -469,6 +474,21 @@ export default function Estimate({}) {
} else { } else {
item.check = false item.check = false
} }
} else {
let codes = item.code.split('、')
let flg = '0'
if (codes.length > 1) {
for (let i = 0; i < pushData.length; i++) {
if (codes.indexOf(pushData[i]) > -1) {
flg = '1'
}
}
if (flg === '1') {
item.check = true
} else {
item.check = false
}
}
} }
}) })
@ -481,8 +501,8 @@ export default function Estimate({}) {
//Pricing //Pricing
const handlePricing = async (showPriceCd) => { const handlePricing = async (showPriceCd) => {
const param = { const param = {
saleStoreId: session.storeId, saleStoreId: estimateContextState.sapSaleStoreId,
sapSalesStoreCd: session.custCd, sapSalesStoreCd: estimateContextState.sapSalesStoreCd,
docTpCd: estimateContextState.estimateType, docTpCd: estimateContextState.estimateType,
priceCd: showPriceCd, priceCd: showPriceCd,
itemIdList: estimateContextState.itemList.filter((item) => item.delFlg === '0' && item.paDispOrder === null), itemIdList: estimateContextState.itemList.filter((item) => item.delFlg === '0' && item.paDispOrder === null),
@ -506,7 +526,6 @@ export default function Estimate({}) {
}) })
} }
} }
setIsGlobalLoading(true) setIsGlobalLoading(true)
await promisePost({ url: '/api/estimate/price/item-price-list', data: param }).then((res) => { await promisePost({ url: '/api/estimate/price/item-price-list', data: param }).then((res) => {
let updateList = [] let updateList = []

View File

@ -45,8 +45,11 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
options.sort = options.sort ?? true options.sort = options.sort ?? true
options.parentId = options.parentId ?? null options.parentId = options.parentId ?? null
this.isSortedPoints = false
if (!options.sort && points.length <= 8) { if (!options.sort && points.length <= 8) {
points = sortedPointLessEightPoint(points) points = sortedPointLessEightPoint(points)
this.isSortedPoints = true
} else { } else {
let isDiagonal = false let isDiagonal = false
points.forEach((point, i) => { points.forEach((point, i) => {
@ -62,6 +65,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
if (!isDiagonal) { if (!isDiagonal) {
points = sortedPoints(points) points = sortedPoints(points)
this.isSortedPoints = true
} }
} }
@ -119,10 +123,12 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
this.addLengthText() this.addLengthText()
this.on('moving', () => { this.on('moving', () => {
this.initLines()
this.addLengthText() this.addLengthText()
}) })
this.on('modified', (e) => { this.on('modified', (e) => {
this.initLines()
this.addLengthText() this.addLengthText()
}) })
@ -183,8 +189,8 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
this.lines = [] this.lines = []
this.points.forEach((point, i) => { this.getCurrentPoints().forEach((point, i) => {
const nextPoint = this.points[(i + 1) % this.points.length] const nextPoint = this.getCurrentPoints()[(i + 1) % this.points.length]
const line = new QLine([point.x, point.y, nextPoint.x, nextPoint.y], { const line = new QLine([point.x, point.y, nextPoint.x, nextPoint.y], {
stroke: this.stroke, stroke: this.stroke,
strokeWidth: this.strokeWidth, strokeWidth: this.strokeWidth,

View File

@ -30,6 +30,7 @@ import { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu' import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
import { useEvent } from '@/hooks/useEvent' import { useEvent } from '@/hooks/useEvent'
import { compasDegAtom } from '@/store/orientationAtom' import { compasDegAtom } from '@/store/orientationAtom'
import { hotkeyStore } from '@/store/hotkeyAtom'
export default function CanvasFrame() { export default function CanvasFrame() {
const canvasRef = useRef(null) const canvasRef = useRef(null)
@ -110,6 +111,38 @@ export default function CanvasFrame() {
resetPcsCheckState() resetPcsCheckState()
} }
/**
* 캔버스가 있을 경우 핫키 이벤트 처리
* hotkeyStore에 핫키 이벤트 리스너 추가
*
* const hotkeys = [
{ key: 'c', fn: () => asdf() },
{ key: 'v', fn: () => qwer() },
]
setHotkeyStore(hotkeys)
*/
const hotkeyState = useRecoilValue(hotkeyStore)
const hotkeyHandlerRef = useRef(null)
useEffect(() => {
hotkeyHandlerRef.current = (e) => {
hotkeyState.forEach((hotkey) => {
if (e.key === hotkey.key) {
hotkey.fn()
}
})
}
document.addEventListener('keyup', hotkeyHandlerRef.current)
return () => {
if (hotkeyHandlerRef.current) {
document.removeEventListener('keyup', hotkeyHandlerRef.current)
}
}
}, [hotkeyState])
/** 핫키 이벤트 처리 끝 */
return ( return (
<div className="canvas-frame"> <div className="canvas-frame">
<canvas ref={canvasRef} id="canvas" style={{ position: 'relative' }}></canvas> <canvas ref={canvasRef} id="canvas" style={{ position: 'relative' }}></canvas>

View File

@ -634,7 +634,7 @@ export default function CanvasMenu(props) {
onClick={() => setEstimatePopupOpen(true)} onClick={() => setEstimatePopupOpen(true)}
> >
<span className="ico ico01"></span> <span className="ico ico01"></span>
<span className="name">{getMessage('plan.menu.estimate.docDown')}</span> <span className="name">{getMessage('plan.menu.estimate.docDownload')}</span>
</button> </button>
<button type="button" style={{ display: saveButtonStyle }} className="btn-frame gray ico-flx" onClick={handleEstimateSubmit}> <button type="button" style={{ display: saveButtonStyle }} className="btn-frame gray ico-flx" onClick={handleEstimateSubmit}>
<span className="ico ico02"></span> <span className="ico ico02"></span>

View File

@ -479,7 +479,7 @@ export default function CircuitTrestleSetting({ id }) {
console.log(stepUpListData) console.log(stepUpListData)
stepUpListData[0].pcsItemList.map((item, index) => { stepUpListData[0].pcsItemList.map((item, index) => {
return item.serQtyList return item.serQtyList
.filter((serQty) => serQty.selected) .filter((serQty) => serQty.selected && serQty.paralQty > 0)
.forEach((serQty) => { .forEach((serQty) => {
pcs.push({ pcs.push({
pcsMkrCd: item.pcsMkrCd, pcsMkrCd: item.pcsMkrCd,

View File

@ -573,7 +573,7 @@ export default function StepUp(props) {
value={seletedMainOption} value={seletedMainOption}
sourceKey="code" sourceKey="code"
targetKey="code" targetKey="code"
showKey="name" showKey={`${globalLocale === 'ja' ? 'nameJp' : 'name'}`}
onChange={(e) => setSeletedMainOption(e)} onChange={(e) => setSeletedMainOption(e)}
/> />
)} )}
@ -586,7 +586,7 @@ export default function StepUp(props) {
value={seletedSubOption} value={seletedSubOption}
sourceKey="code" sourceKey="code"
targetKey="code" targetKey="code"
showKey="name" showKey={`${globalLocale === 'ja' ? 'nameJp' : 'name'}`}
onChange={(e) => setSeletedSubOption(e)} onChange={(e) => setSeletedSubOption(e)}
/> />
)} )}

View File

@ -7,6 +7,7 @@ import { useMessage } from '@/hooks/useMessage'
import { usePopup } from '@/hooks/usePopup' import { usePopup } from '@/hooks/usePopup'
import { canvasState } from '@/store/canvasAtom' import { canvasState } from '@/store/canvasAtom'
import { usePolygon } from '@/hooks/usePolygon' import { usePolygon } from '@/hooks/usePolygon'
import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch'
const FLOW_DIRECTION_TYPE = { const FLOW_DIRECTION_TYPE = {
EIGHT_AZIMUTH: 'eightAzimuth', EIGHT_AZIMUTH: 'eightAzimuth',
@ -19,6 +20,8 @@ export default function FlowDirectionSetting(props) {
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { changeSurfaceLineType } = useSurfaceShapeBatch({})
useEffect(() => { useEffect(() => {
return () => { return () => {
canvas?.discardActiveObject() canvas?.discardActiveObject()
@ -53,6 +56,7 @@ export default function FlowDirectionSetting(props) {
}) })
drawDirectionArrow(roof) drawDirectionArrow(roof)
canvas?.renderAll() canvas?.renderAll()
changeSurfaceLineType(roof)
closePopup(id) closePopup(id)
} }

View File

@ -19,6 +19,7 @@ import { getChonByDegree, getDegreeByChon } from '@/util/canvas-util'
import { usePolygon } from '@/hooks/usePolygon' import { usePolygon } from '@/hooks/usePolygon'
import { canvasState } from '@/store/canvasAtom' import { canvasState } from '@/store/canvasAtom'
import { useRoofFn } from '@/hooks/common/useRoofFn' import { useRoofFn } from '@/hooks/common/useRoofFn'
import { usePlan } from '@/hooks/usePlan'
/** /**
* 지붕 레이아웃 * 지붕 레이아웃
@ -45,6 +46,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
const { setSurfaceShapePattern } = useRoofFn() const { setSurfaceShapePattern } = useRoofFn()
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
const roofDisplay = useRecoilValue(roofDisplaySelector) const roofDisplay = useRecoilValue(roofDisplaySelector)
const { saveCanvas } = usePlan()
const roofRef = { const roofRef = {
roofCd: useRef(null), roofCd: useRef(null),
@ -205,7 +207,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
/** /**
* 배치면초기설정 저장 버튼 클릭 * 배치면초기설정 저장 버튼 클릭
*/ */
const handleSaveBtn = () => { const handleSaveBtn = async () => {
const roofInfo = { const roofInfo = {
...currentRoof, ...currentRoof,
planNo: basicSetting.planNo, planNo: basicSetting.planNo,
@ -254,6 +256,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
/* 저장 후 화면 닫기 */ /* 저장 후 화면 닫기 */
closePopup(id) closePopup(id)
await saveCanvas(false)
} }
return ( return (
@ -271,7 +274,11 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
<tbody> <tbody>
<tr> <tr>
<th>{getMessage('modal.placement.initial.setting.plan.drawing')}</th> <th>{getMessage('modal.placement.initial.setting.plan.drawing')}</th>
<td>{getMessage('modal.placement.initial.setting.plan.drawing.size.stuff')}</td> <td>
{getMessage('modal.placement.initial.setting.plan.drawing.size.stuff')}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
{getMessage('modal.placement.initial.setting.plan.drawing.only.number')}
</td>
</tr> </tr>
<tr> <tr>
<th> <th>

View File

@ -136,7 +136,7 @@ export const useEstimateController = (planNo, flag) => {
} }
useEffect(() => { useEffect(() => {
setEstimateData({ ...estimateContextState, userId: session.userId, sapSalesStoreCd: session.custCd }) setEstimateData({ ...estimateContextState, userId: session.userId })
}, [estimateContextState]) }, [estimateContextState])
// 첨부파일 다운로드 // 첨부파일 다운로드
@ -452,8 +452,6 @@ export const useEstimateController = (planNo, flag) => {
} }
const params = { const params = {
saleStoreId: session.storeId,
sapSalesStoreCd: session.custCd,
objectNo: estimateData.objectNo, objectNo: estimateData.objectNo,
planNo: sendPlanNo, planNo: sendPlanNo,
copySaleStoreId: otherSaleStoreId ? otherSaleStoreId : saleStoreId, copySaleStoreId: otherSaleStoreId ? otherSaleStoreId : saleStoreId,
@ -462,24 +460,30 @@ export const useEstimateController = (planNo, flag) => {
} }
setIsGlobalLoading(true) setIsGlobalLoading(true)
await promisePost({ url: '/api/estimate/save-estimate-copy', data: params }).then((res) => { await promisePost({ url: '/api/estimate/save-estimate-copy', data: params })
setIsGlobalLoading(false) .then((res) => {
if (res.status === 201) {
if (isObjectNotEmpty(res.data)) {
let newObjectNo = res.data.objectNo
swalFire({
text: getMessage('estimate.detail.estimateCopyPopup.copy.alertMessage'),
type: 'alert',
confirmFn: () => {
setEstimateCopyPopupOpen(false) //팝업닫고
router.push(`/management/stuff/detail?objectNo=${newObjectNo.toString()}`, { scroll: false })
},
})
}
} else {
setIsGlobalLoading(false) setIsGlobalLoading(false)
} if (res.status === 201) {
}) if (isObjectNotEmpty(res.data)) {
let newObjectNo = res.data.objectNo
swalFire({
text: getMessage('estimate.detail.estimateCopyPopup.copy.alertMessage'),
type: 'alert',
confirmFn: () => {
setEstimateCopyPopupOpen(false) //팝업닫고
router.push(`/management/stuff/detail?objectNo=${newObjectNo.toString()}`, { scroll: false })
},
})
}
} else {
setIsGlobalLoading(false)
}
})
.catch((e) => {
console.error('캔버스 복사 중 오류 발생')
swalFire({ text: getMessage('estimate.detail.estimateCopyPopup.copy.alertMessageError'), type: 'alert', icon: 'error' })
setIsGlobalLoading(false)
})
} }
const checkLength = (value) => { const checkLength = (value) => {

View File

@ -549,6 +549,7 @@ export function useModuleBasicSetting(tabNum) {
* 스냅기능 * 스냅기능
*/ */
let snapDistance = flowDirection === 'south' || flowDirection === 'north' ? 70 : 40 let snapDistance = flowDirection === 'south' || flowDirection === 'north' ? 70 : 40
let sideSnapDistance = 15
let trestleSnapDistance = 15 let trestleSnapDistance = 15
let intvHor = let intvHor =
@ -588,51 +589,102 @@ export function useModuleBasicSetting(tabNum) {
const holdCellCenterX = holdCellLeft + toFixedWithoutRounding(cell.width / 2, 2) const holdCellCenterX = holdCellLeft + toFixedWithoutRounding(cell.width / 2, 2)
const holdCellCenterY = holdCellTop + toFixedWithoutRounding(cell.height / 2, 2) const holdCellCenterY = holdCellTop + toFixedWithoutRounding(cell.height / 2, 2)
//흐름방향따라 달라야 한다.
if (flowDirection === 'south' || flowDirection === 'north') {
if (Math.abs(smallCenterX - holdCellCenterX) < snapDistance) {
//움직이는 모듈 가운데 -> 설치 모듈 가운데
tempModule.left = holdCellCenterX - toFixedWithoutRounding(width / 2, 2)
}
//움직이는 모듈왼쪽 -> 가운데
if (Math.abs(smallLeft - holdCellCenterX) < snapDistance) {
tempModule.left = holdCellCenterX + intvHor / 2
}
// 오른쪽 -> 가운데
if (Math.abs(smallRight - holdCellCenterX) < snapDistance) {
tempModule.left = holdCellCenterX - width - intvHor / 2
}
//설치된 셀에 좌측에 스냅
if (Math.abs(smallRight - holdCellLeft) < snapDistance) {
// console.log('모듈 좌측 스냅')
tempModule.left = holdCellLeft - width - intvHor
}
//설치된 셀에 우측에 스냅
if (Math.abs(smallLeft - holdCellRight) < snapDistance) {
// console.log('모듈 우측 스냅')
tempModule.left = holdCellRight + intvHor
}
//설치된 셀에 위쪽에 스냅
if (Math.abs(smallBottom - holdCellTop) < sideSnapDistance) {
tempModule.top = holdCellTop - height - intvVer
}
//설치된 셀에 밑쪽에 스냅
if (Math.abs(smallTop - holdCellBottom) < sideSnapDistance) {
tempModule.top = holdCellBottom + intvVer
}
//설치된 셀에 윗쪽에 스냅
if (Math.abs(smallTop - holdCellTop) < sideSnapDistance) {
tempModule.top = holdCellTop
}
} else {
//흐름방향 west, east
//가운데 가운데
if (Math.abs(smallCenterY - holdCellCenterY) < snapDistance) {
tempModule.top = holdCellCenterY - toFixedWithoutRounding(width / 2, 2)
}
//위쪽 -> 가운데
if (Math.abs(smallTop - holdCellCenterY) < snapDistance) {
// console.log('holdCellCenterX', holdCellCenterX)
// console.log('smallLeft', smallLeft)
// console.log('모듈 센터에 스냅')
tempModule.top = holdCellCenterY + intvHor / 2
// console.log('tempModule.left', tempModule.left)
}
// 밑 -> 가운데
if (Math.abs(smallBottom - holdCellCenterY) < snapDistance) {
tempModule.top = holdCellCenterY - height - intvHor / 2
}
//설치된 셀에 좌측에 스냅
if (Math.abs(smallRight - holdCellLeft) < snapDistance) {
// console.log('모듈 좌측 스냅')
tempModule.left = holdCellLeft - width - intvHor
}
//설치된 셀에 우측에 스냅
if (Math.abs(smallLeft - holdCellRight) < snapDistance) {
// console.log('모듈 우측 스냅')
tempModule.left = holdCellRight + intvHor
}
//설치된 셀에 위쪽에 스냅
if (Math.abs(smallBottom - holdCellTop) < sideSnapDistance) {
tempModule.top = holdCellTop - height - intvVer
}
//설치된 셀에 밑쪽에 스냅
if (Math.abs(smallTop - holdCellBottom) < sideSnapDistance) {
tempModule.top = holdCellBottom + intvVer
}
//설치된 셀에 윗쪽에 스냅
if (Math.abs(smallTop - holdCellTop) < sideSnapDistance) {
tempModule.top = holdCellTop
}
if (Math.abs(smallLeft - holdCellLeft) < snapDistance) {
tempModule.left = holdCellLeft
}
}
//가운데 -> 가운대 //가운데 -> 가운대
if (Math.abs(smallCenterX - holdCellCenterX) < snapDistance) {
tempModule.left = holdCellCenterX - toFixedWithoutRounding(width / 2, 2)
}
//왼쪽 -> 가운데
if (Math.abs(smallLeft - holdCellCenterX) < snapDistance) {
// console.log('holdCellCenterX', holdCellCenterX)
// console.log('smallLeft', smallLeft)
// console.log('모듈 센터에 스냅')
tempModule.left = holdCellCenterX + intvHor / 2
// console.log('tempModule.left', tempModule.left)
}
// 오른쪽 -> 가운데
if (Math.abs(smallRight - holdCellCenterX) < snapDistance) {
tempModule.left = holdCellCenterX - width - intvHor / 2
}
//설치된 셀에 좌측에 스냅
if (Math.abs(smallRight - holdCellLeft) < snapDistance) {
// console.log('모듈 좌측 스냅')
tempModule.left = holdCellLeft - width - intvHor
}
//설치된 셀에 우측에 스냅
if (Math.abs(smallLeft - holdCellRight) < snapDistance) {
// console.log('모듈 우측 스냅')
tempModule.left = holdCellRight + intvHor
}
//설치된 셀에 위쪽에 스냅
if (Math.abs(smallBottom - holdCellTop) < 10) {
tempModule.top = holdCellTop - height - intvVer
}
//설치된 셀에 밑쪽에 스냅
if (Math.abs(smallTop - holdCellBottom) < 10) {
tempModule.top = holdCellBottom + intvVer
}
//설치된 셀에 윗쪽에 스냅
if (Math.abs(smallTop - holdCellTop) < 10) {
tempModule.top = holdCellTop
}
//세로 가운데 -> 가운데 //세로 가운데 -> 가운데
// if (Math.abs(smallCenterY - holdCellCenterY) < snapDistance) { // if (Math.abs(smallCenterY - holdCellCenterY) < snapDistance) {
@ -641,11 +693,6 @@ export function useModuleBasicSetting(tabNum) {
// //위쪽 -> 가운데 // //위쪽 -> 가운데
// if (Math.abs(smallTop - holdCellCenterY) < cellSnapDistance) { // if (Math.abs(smallTop - holdCellCenterY) < cellSnapDistance) {
// tempModule.top = holdCellCenterY // tempModule.top = holdCellCenterY
// }
// //아랫쪽 -> 가운데
// if (Math.abs(smallBottom - holdCellCenterY) < cellSnapDistance) {
// tempModule.top = holdCellCenterY - height
// }
}) })
} }
@ -673,22 +720,19 @@ export function useModuleBasicSetting(tabNum) {
// if (Math.abs(smallLeft - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < snapDistance) { // if (Math.abs(smallLeft - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < snapDistance) {
// tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 // tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2
// } // }
// 모듈이 가운데가 세로중앙선에 붙게 스냅 // 모듈이 가운데가 세로중앙선에 붙게 스냅
if (Math.abs(smallCenterX - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < trestleSnapDistance) { // if (Math.abs(smallCenterX - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < trestleSnapDistance) {
tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 - tempModule.width / 2 // tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 - tempModule.width / 2
} // }
// 모듈오른쪽이 세로중앙선에 붙게 스냅 // 모듈오른쪽이 세로중앙선에 붙게 스냅
// if (Math.abs(smallRight - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < trestleSnapDistance) { // if (Math.abs(smallRight - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < trestleSnapDistance) {
// tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 - tempModule.width * tempModule.scaleX // tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 - tempModule.width * tempModule.scaleX
// } // }
} else { } else {
// 모듈이 가로중앙선에 스냅 // 모듈이 가로중앙선에 스냅
if (Math.abs(smallTop + tempModule.height / 2 - bigCenterY) < trestleSnapDistance) { // if (Math.abs(smallTop + tempModule.height / 2 - bigCenterY) < trestleSnapDistance) {
tempModule.top = bigCenterY - tempModule.height / 2 // tempModule.top = bigCenterY - tempModule.height / 2
} // }
// if (Math.abs(smallTop - (trestleTop + moduleSetupSurfaces[i].height / 2)) < trestleSnapDistance) { // if (Math.abs(smallTop - (trestleTop + moduleSetupSurfaces[i].height / 2)) < trestleSnapDistance) {
// tempModule.top = trestleTop + moduleSetupSurfaces[i].height / 2 // tempModule.top = trestleTop + moduleSetupSurfaces[i].height / 2
// } // }

View File

@ -1,35 +0,0 @@
import { useEffect, useState } from 'react'
import { useRecoilValue, useSetRecoilState } from 'recoil'
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
import { useMasterController } from '@/hooks/common/useMasterController'
import { canvasSettingState, canvasState, currentCanvasPlanState, moduleSetupSurfaceState } from '@/store/canvasAtom'
import { POLYGON_TYPE, BATCH_TYPE } from '@/common/common'
import { useRoofFn } from '@/hooks/common/useRoofFn'
import { roofDisplaySelector } from '@/store/settingAtom'
import offsetPolygon from '@/util/qpolygon-utils'
import { v4 as uuidv4 } from 'uuid'
import { QPolygon } from '@/components/fabric/QPolygon'
import { useEvent } from '@/hooks/useEvent'
import { useSwal } from '@/hooks/useSwal'
import { useMessage } from '@/hooks/useMessage'
export function useModulePlace() {
const canvas = useRecoilValue(canvasState)
const moduleSelectionData = useRecoilValue(moduleSelectionDataState)
const [trestleDetailParams, setTrestleDetailParams] = useState([])
const [trestleDetailList, setTrestleDetailList] = useState([])
const selectedModules = useRecoilValue(selectedModuleState)
const { getTrestleDetailList } = useMasterController()
const canvasSetting = useRecoilValue(canvasSettingState)
const { setSurfaceShapePattern } = useRoofFn()
const roofDisplay = useRecoilValue(roofDisplaySelector)
const { addTargetMouseEventListener } = useEvent()
const setModuleSetupSurface = useSetRecoilState(moduleSetupSurfaceState)
const [saleStoreNorthFlg, setSaleStoreNorthFlg] = useState(false)
const { swalFire } = useSwal()
const { getMessage } = useMessage()
return {
selectedModules,
}
}

View File

@ -10,6 +10,7 @@ import { useSwal } from '@/hooks/useSwal'
import { useContext } from 'react' import { useContext } from 'react'
import { QcastContext } from '@/app/QcastProvider' import { QcastContext } from '@/app/QcastProvider'
import { useCircuitTrestle } from '@/hooks/useCirCuitTrestle' import { useCircuitTrestle } from '@/hooks/useCirCuitTrestle'
import { useMessage } from '@/hooks/useMessage'
// 모듈간 같은 행, 열의 마진이 10 이하인 경우는 같은 행, 열로 간주 // 모듈간 같은 행, 열의 마진이 10 이하인 경우는 같은 행, 열로 간주
const MODULE_MARGIN = 10 const MODULE_MARGIN = 10
@ -26,6 +27,7 @@ export const useTrestle = () => {
const { getSelectedPcsItemList } = useCircuitTrestle() const { getSelectedPcsItemList } = useCircuitTrestle()
const { resetCircuits } = useCircuitTrestle() const { resetCircuits } = useCircuitTrestle()
const { getMessage } = useMessage()
const apply = () => { const apply = () => {
const notAllocationModules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE && !obj.circuit) const notAllocationModules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE && !obj.circuit)
@ -58,7 +60,6 @@ export const useTrestle = () => {
} }
const construction = moduleSelectionData?.roofConstructions?.find((construction) => construction.roofIndex === roofMaterialIndex).construction const construction = moduleSelectionData?.roofConstructions?.find((construction) => construction.roofIndex === roofMaterialIndex).construction
if (!construction) { if (!construction) {
swalFire({ text: 'construction 존재안함', icon: 'error' })
return return
} }
@ -131,9 +132,9 @@ export const useTrestle = () => {
surface.isChidory = isChidory surface.isChidory = isChidory
if (plvrYn === 'N' && isChidory) { if (plvrYn === 'N' && isChidory) {
swalFire({ text: '치조불가공법입니다.', icon: 'error' }) swalFire({ text: getMessage('chidory.can.not.install'), icon: 'error' })
clear() clear()
throw new Error('치조불가공법입니다.') throw new Error(getMessage('chidory.can.not.install'))
} }
surface.set({ isChidory: isChidory }) surface.set({ isChidory: isChidory })

View File

@ -84,7 +84,7 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) {
// innerLines가 있을경우 삭제 // innerLines가 있을경우 삭제
const roofs = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) const roofs = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
if (roofs.length === 0) { if (roofs.length === 0) {
swalFire({ text: '지붕형상이 없습니다.' }) swalFire({ text: getMessage('roof.line.not.found') })
closePopup(id) closePopup(id)
return return
} }

View File

@ -52,7 +52,7 @@ export function useEavesGableEdit(id) {
useEffect(() => { useEffect(() => {
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
if (!outerLineFix || outerLines.length === 0) { if (!outerLineFix || outerLines.length === 0) {
swalFire({ text: '외벽선이 없습니다.' }) swalFire({ text: getMessage('wall.line.not.found') })
closePopup(id) closePopup(id)
} }
}, []) }, [])

View File

@ -1,6 +1,6 @@
import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil' import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'
import { canvasState, currentAngleTypeSelector, currentMenuState, currentObjectState } from '@/store/canvasAtom' import { canvasState, currentAngleTypeSelector, currentMenuState, currentObjectState } from '@/store/canvasAtom'
import { useEffect, useRef, useState } from 'react' import { useContext, useEffect, useRef, useState } from 'react'
import { useAxios } from '@/hooks/useAxios' import { useAxios } from '@/hooks/useAxios'
import { useSwal } from '@/hooks/useSwal' import { useSwal } from '@/hooks/useSwal'
import { usePolygon } from '@/hooks/usePolygon' import { usePolygon } from '@/hooks/usePolygon'
@ -26,6 +26,8 @@ import { getChonByDegree, getDegreeByChon } from '@/util/canvas-util'
import { moduleSelectionDataState } from '@/store/selectedModuleOptions' import { moduleSelectionDataState } from '@/store/selectedModuleOptions'
import { useCanvasPopupStatusController } from '@/hooks/common/useCanvasPopupStatusController' import { useCanvasPopupStatusController } from '@/hooks/common/useCanvasPopupStatusController'
import { outerLinePointsState } from '@/store/outerLineAtom' import { outerLinePointsState } from '@/store/outerLineAtom'
import { QcastContext } from '@/app/QcastProvider'
import { usePlan } from '@/hooks/usePlan'
export function useRoofAllocationSetting(id) { export function useRoofAllocationSetting(id) {
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
@ -49,8 +51,9 @@ export function useRoofAllocationSetting(id) {
const { get, post } = useAxios(globalLocaleState) const { get, post } = useAxios(globalLocaleState)
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { swalFire } = useSwal() const { swalFire } = useSwal()
const { setIsGlobalLoading } = useContext(QcastContext)
const { setSurfaceShapePattern } = useRoofFn() const { setSurfaceShapePattern } = useRoofFn()
const { saveCanvas } = usePlan()
const [moduleSelectionData, setModuleSelectionData] = useRecoilState(moduleSelectionDataState) const [moduleSelectionData, setModuleSelectionData] = useRecoilState(moduleSelectionDataState)
const resetPoints = useResetRecoilState(outerLinePointsState) const resetPoints = useResetRecoilState(outerLinePointsState)
@ -200,6 +203,7 @@ export function useRoofAllocationSetting(id) {
*/ */
const basicSettingSave = async () => { const basicSettingSave = async () => {
try { try {
setIsGlobalLoading(true)
const patternData = { const patternData = {
objectNo: correntObjectNo, objectNo: correntObjectNo,
planNo: Number(basicSetting.planNo), planNo: Number(basicSetting.planNo),
@ -222,6 +226,8 @@ export function useRoofAllocationSetting(id) {
await post({ url: `/api/canvas-management/roof-allocation-settings`, data: patternData }).then((res) => { await post({ url: `/api/canvas-management/roof-allocation-settings`, data: patternData }).then((res) => {
swalFire({ text: getMessage(res.returnMessage) }) swalFire({ text: getMessage(res.returnMessage) })
setIsGlobalLoading(false)
saveCanvas(false)
}) })
//Recoil 설정 //Recoil 설정
@ -258,6 +264,15 @@ export function useRoofAllocationSetting(id) {
* 지붕재 삭제 * 지붕재 삭제
*/ */
const onDeleteRoofMaterial = (idx) => { const onDeleteRoofMaterial = (idx) => {
const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
for (let i = 0; i < roofs.length; i++) {
if (roofs[i].roofMaterial.index === idx) {
swalFire({ type: 'alert', icon: 'error', text: getMessage('roof.material.can.not.delete') })
return
}
}
const isSelected = currentRoofList[idx].selected const isSelected = currentRoofList[idx].selected
const newRoofList = JSON.parse(JSON.stringify(currentRoofList)).filter((_, index) => index !== idx) const newRoofList = JSON.parse(JSON.stringify(currentRoofList)).filter((_, index) => index !== idx)
if (isSelected) { if (isSelected) {
@ -270,8 +285,6 @@ export function useRoofAllocationSetting(id) {
* 선택한 지붕재로 할당 * 선택한 지붕재로 할당
*/ */
const handleSave = () => { const handleSave = () => {
basicSettingSave()
/** /**
* 모두 actualSize 있으면 바로 적용 없으면 actualSize 설정 * 모두 actualSize 있으면 바로 적용 없으면 actualSize 설정
*/ */
@ -280,6 +293,7 @@ export function useRoofAllocationSetting(id) {
} else { } else {
apply() apply()
resetPoints() resetPoints()
basicSettingSave()
} }
} }
@ -287,7 +301,6 @@ export function useRoofAllocationSetting(id) {
* 지붕재 오른쪽 마우스 클릭 단일로 지붕재 변경 필요한 경우 * 지붕재 오른쪽 마우스 클릭 단일로 지붕재 변경 필요한 경우
*/ */
const handleSaveContext = () => { const handleSaveContext = () => {
basicSettingSave()
const newRoofList = currentRoofList.map((roof, idx) => { const newRoofList = currentRoofList.map((roof, idx) => {
return { ...roof, index: idx, raft: roof.raft ? roof.raft : roof.raftBaseCd } return { ...roof, index: idx, raft: roof.raft ? roof.raft : roof.raftBaseCd }
}) })
@ -299,11 +312,28 @@ export function useRoofAllocationSetting(id) {
}) })
setRoofList(newRoofList) setRoofList(newRoofList)
setRoofMaterials(newRoofList)
const selectedRoofMaterial = newRoofList.find((roof) => roof.selected) const selectedRoofMaterial = newRoofList.find((roof) => roof.selected)
setSurfaceShapePattern(currentObject, roofDisplay.column, false, selectedRoofMaterial, true) setSurfaceShapePattern(currentObject, roofDisplay.column, false, selectedRoofMaterial, true)
drawDirectionArrow(currentObject) drawDirectionArrow(currentObject)
modifyModuleSelectionData() modifyModuleSelectionData()
closeAll() closeAll()
basicSettingSave()
}
/**
* 기존 세팅된 지붕에 지붕재 내용을 바뀐 내용으로 수정
* @param newRoofMaterials
*/
const setRoofMaterials = (newRoofMaterials) => {
const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
newRoofMaterials.forEach((roofMaterial) => {
const index = roofMaterial.index
const tempRoofs = roofs.filter((roof) => roof.roofMaterial?.index === index)
tempRoofs.forEach((roof) => {
setSurfaceShapePattern(roof, roofDisplay.column, false, roofMaterial)
})
})
} }
/** /**
@ -361,6 +391,7 @@ export function useRoofAllocationSetting(id) {
splitPolygonWithLines(roofBase) splitPolygonWithLines(roofBase)
} }
} catch (e) { } catch (e) {
console.log(e)
return return
} }
@ -408,6 +439,8 @@ export function useRoofAllocationSetting(id) {
drawDirectionArrow(roof) drawDirectionArrow(roof)
}) })
setRoofMaterials(newRoofList)
/** 외곽선 삭제 */ /** 외곽선 삭제 */
const removeTargets = canvas.getObjects().filter((obj) => obj.name === 'outerLinePoint' || obj.name === 'outerLine') const removeTargets = canvas.getObjects().filter((obj) => obj.name === 'outerLinePoint' || obj.name === 'outerLine')
removeTargets.forEach((obj) => { removeTargets.forEach((obj) => {

View File

@ -51,7 +51,7 @@ export function useRoofShapePassivitySetting(id) {
useEffect(() => { useEffect(() => {
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
if (!outerLineFix || outerLines.length === 0) { if (!outerLineFix || outerLines.length === 0) {
swalFire({ text: '외벽선이 없습니다.' }) swalFire({ text: getMessage('wall.line.not.found') })
closePopup(id) closePopup(id)
return return
} }

View File

@ -191,7 +191,7 @@ export function useRoofShapeSetting(id) {
let direction let direction
if (outerLines.length < 2) { if (outerLines.length < 2) {
swalFire({ text: '외벽선이 없습니다.', icon: 'error' }) swalFire({ text: getMessage('wall.line.not.found') })
return return
} }

View File

@ -60,7 +60,7 @@ export function useWallLineOffsetSetting(id) {
useEffect(() => { useEffect(() => {
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
if (outerLines.length === 0) { if (outerLines.length === 0) {
swalFire({ text: '외벽선이 없습니다.' }) swalFire({ text: getMessage('wall.line.not.found') })
closePopup(id) closePopup(id)
return return
} }

View File

@ -32,6 +32,7 @@ import {
import { usePolygon } from '@/hooks/usePolygon' import { usePolygon } from '@/hooks/usePolygon'
import { POLYGON_TYPE } from '@/common/common' import { POLYGON_TYPE } from '@/common/common'
import { usePopup } from '@/hooks/usePopup' import { usePopup } from '@/hooks/usePopup'
import { useSurfaceShapeBatch } from './useSurfaceShapeBatch'
import { roofDisplaySelector } from '@/store/settingAtom' import { roofDisplaySelector } from '@/store/settingAtom'
import { useRoofFn } from '@/hooks/common/useRoofFn' import { useRoofFn } from '@/hooks/common/useRoofFn'
@ -50,6 +51,8 @@ export function usePlacementShapeDrawing(id) {
const { addPolygonByLines, drawDirectionArrow } = usePolygon() const { addPolygonByLines, drawDirectionArrow } = usePolygon()
const { tempGridMode } = useTempGrid() const { tempGridMode } = useTempGrid()
const { setSurfaceShapePattern } = useRoofFn() const { setSurfaceShapePattern } = useRoofFn()
const { changeSurfaceLineType } = useSurfaceShapeBatch({})
const canvasSetting = useRecoilValue(canvasSettingState) const canvasSetting = useRecoilValue(canvasSettingState)
const verticalHorizontalMode = useRecoilValue(verticalHorizontalModeState) const verticalHorizontalMode = useRecoilValue(verticalHorizontalModeState)
const adsorptionPointAddMode = useRecoilValue(adsorptionPointAddModeState) const adsorptionPointAddMode = useRecoilValue(adsorptionPointAddModeState)
@ -253,11 +256,14 @@ export function usePlacementShapeDrawing(id) {
setPoints([]) setPoints([])
canvas?.renderAll() canvas?.renderAll()
if (+canvasSetting?.roofSizeSet === 3) { // if (+canvasSetting?.roofSizeSet === 3) {
closePopup(id) // closePopup(id)
return // return
} // }
addPopup(id, 1, <PlacementSurfaceLineProperty id={id} roof={roof} />, false) // addPopup(id, 1, <PlacementSurfaceLineProperty id={id} roof={roof} />, false)
changeSurfaceLineType(roof)
closePopup(id)
} }
if (points.length < 3) { if (points.length < 3) {

View File

@ -3,7 +3,7 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil' import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
import { canvasSettingState, canvasState, currentCanvasPlanState, globalPitchState } from '@/store/canvasAtom' import { canvasSettingState, canvasState, currentCanvasPlanState, globalPitchState } from '@/store/canvasAtom'
import { MENU, POLYGON_TYPE } from '@/common/common' import { MENU, POLYGON_TYPE, LINE_TYPE } from '@/common/common'
import { getIntersectionPoint, toFixedWithoutRounding } from '@/util/canvas-util' import { getIntersectionPoint, toFixedWithoutRounding } from '@/util/canvas-util'
import { degreesToRadians } from '@turf/turf' import { degreesToRadians } from '@turf/turf'
import { QPolygon } from '@/components/fabric/QPolygon' import { QPolygon } from '@/components/fabric/QPolygon'
@ -111,7 +111,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
lockScalingX: true, // X 축 크기 조정 잠금 lockScalingX: true, // X 축 크기 조정 잠금
lockScalingY: true, // Y 축 크기 조정 잠금 lockScalingY: true, // Y 축 크기 조정 잠금
name: MENU.BATCH_CANVAS.SURFACE_SHAPE_BATCH_TEMP, name: MENU.BATCH_CANVAS.SURFACE_SHAPE_BATCH_TEMP,
flipX: xInversion !== yInversion, // flipX: xInversion !== yInversion,
// angle: xInversion && yInversion ? Math.abs((rotate + 180) % 360) : Math.abs(rotate), // angle: xInversion && yInversion ? Math.abs((rotate + 180) % 360) : Math.abs(rotate),
// angle: rotate, // angle: rotate,
originX: 'center', originX: 'center',
@ -120,6 +120,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
} }
obj = new QPolygon(points, options) obj = new QPolygon(points, options)
let imageRotate = 0 let imageRotate = 0
if (xInversion && !yInversion) { if (xInversion && !yInversion) {
if (rotate % 180 === 0 || rotate < 0) { if (rotate % 180 === 0 || rotate < 0) {
@ -148,7 +149,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
} else { } else {
imageRotate = (rotate + 360) % 360 imageRotate = (rotate + 360) % 360
} }
obj.set({ angle: imageRotate }) obj.set({ angle: imageRotate, flipX: xInversion !== yInversion })
obj.setCoords() //좌표 변경 적용 obj.setCoords() //좌표 변경 적용
canvas?.add(obj) canvas?.add(obj)
@ -158,6 +159,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
addCanvasMouseEventListener('mouse:down', (e) => { addCanvasMouseEventListener('mouse:down', (e) => {
isDrawing = false isDrawing = false
const { xInversion, yInversion } = surfaceRefs
canvas?.remove(obj) canvas?.remove(obj)
//각도 추가 //각도 추가
@ -178,6 +180,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
} }
//회전, flip등이 먹은 기준으로 새로생성 //회전, flip등이 먹은 기준으로 새로생성
// const batchSurface = addPolygon(reorderedPoints, {
const batchSurface = addPolygon(obj.getCurrentPoints(), { const batchSurface = addPolygon(obj.getCurrentPoints(), {
fill: 'transparent', fill: 'transparent',
stroke: 'red', stroke: 'red',
@ -196,18 +199,25 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
pitch: globalPitch, pitch: globalPitch,
surfaceId: surfaceId, surfaceId: surfaceId,
direction: direction, direction: direction,
isXInversion: xInversion,
isYInversion: yInversion,
}) })
canvas.setActiveObject(batchSurface) canvas.setActiveObject(batchSurface)
setSurfaceShapePattern(batchSurface, roofDisplay.column) setSurfaceShapePattern(batchSurface, roofDisplay.column)
drawDirectionArrow(batchSurface) drawDirectionArrow(batchSurface)
// if (setIsHidden) setIsHidden(false)
// closePopup(id) // closePopup(id)
initEvent() initEvent()
if (+canvasSetting?.roofSizeSet === 3) return // if (+canvasSetting?.roofSizeSet === 3) return
const popupId = uuidv4() // const popupId = uuidv4()
addPopup(popupId, 2, <PlacementSurfaceLineProperty roof={batchSurface} id={popupId} setIsHidden={setIsHidden} />) // addPopup(popupId, 2, <PlacementSurfaceLineProperty roof={batchSurface} id={popupId} setIsHidden={setIsHidden} />)
// console.log('xInversion', xInversion) //상하반전
// console.log('yInversion', yInversion) //좌우반전
changeSurfaceLineType(batchSurface)
if (setIsHidden) setIsHidden(false)
}) })
} else { } else {
if (setIsHidden) setIsHidden(false) if (setIsHidden) setIsHidden(false)
@ -488,18 +498,18 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
} }
case 10: { case 10: {
points = [ points = [
{ x: pointer.x + length1 / 2, y: pointer.y + length4 / 2 },
{ x: pointer.x + length1 / 2 - length1, y: pointer.y + length4 / 2 },
{ x: pointer.x + length1 / 2 - length1, y: pointer.y + length4 / 2 - length5 }, { x: pointer.x + length1 / 2 - length1, y: pointer.y + length4 / 2 - length5 },
{ x: pointer.x + length1 / 2 - length1 + length2, y: pointer.y + length4 / 2 - length5 }, { x: pointer.x + length1 / 2 - length1, y: pointer.y + length4 / 2 },
{ { x: pointer.x + length1 / 2, y: pointer.y + length4 / 2 },
x: pointer.x + length1 / 2 - length1 + length2,
y: pointer.y + length4 / 2 - length5 - (length4 - length5),
},
{ {
x: pointer.x + length1 / 2 - length1 + length2 + length3, x: pointer.x + length1 / 2 - length1 + length2 + length3,
y: pointer.y + length4 / 2 - length5 - (length4 - length5), y: pointer.y + length4 / 2 - length5 - (length4 - length5),
}, },
{
x: pointer.x + length1 / 2 - length1 + length2,
y: pointer.y + length4 / 2 - length5 - (length4 - length5),
},
{ x: pointer.x + length1 / 2 - length1 + length2, y: pointer.y + length4 / 2 - length5 },
] ]
break break
} }
@ -613,27 +623,27 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
} }
case 14: { case 14: {
points = [ points = [
{ x: pointer.x - length1 / 2 + length2, y: pointer.y + length4 / 2 },
{ x: pointer.x - length1 / 2, y: pointer.y + length4 / 2 },
{ x: pointer.x - length1 / 2, y: pointer.y + length4 / 2 - length4 }, { x: pointer.x - length1 / 2, y: pointer.y + length4 / 2 - length4 },
{ x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length4 }, { x: pointer.x - length1 / 2, y: pointer.y + length4 / 2 },
{ x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length4 + length4 }, { x: pointer.x - length1 / 2 + length2, y: pointer.y + length4 / 2 },
{ x: pointer.x - length1 / 2 + length1 - length3, y: pointer.y + length4 / 2 - length4 + length4 },
{ {
x: pointer.x - length1 / 2 + length2 + (length1 - length2 - length3) / 2, x: pointer.x - length1 / 2 + length2 + (length1 - length2 - length3) / 2,
y: pointer.y + length4 / 2 - length4 + length5, y: pointer.y + length4 / 2 - length4 + length5,
}, },
{ x: pointer.x - length1 / 2 + length1 - length3, y: pointer.y + length4 / 2 - length4 + length4 },
{ x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length4 + length4 },
{ x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length4 },
] ]
break break
} }
case 15: { case 15: {
points = [ points = [
{ x: pointer.x - length1 / 2, y: pointer.y + length2 - length2 / 2 },
{ x: pointer.x - length1 / 2, y: pointer.y + length2 - length2 / 2 - length3 }, { x: pointer.x - length1 / 2, y: pointer.y + length2 - length2 / 2 - length3 },
{ x: pointer.x, y: pointer.y + length2 - length2 / 2 - length3 - (length2 - length3) }, { x: pointer.x - length1 / 2, y: pointer.y + length2 - length2 / 2 },
{ x: pointer.x + length1 / 2, y: pointer.y + length2 - length2 / 2 - length3 },
{ x: pointer.x + length1 / 2, y: pointer.y + length2 - length2 / 2 - length3 + length3 }, { x: pointer.x + length1 / 2, y: pointer.y + length2 - length2 / 2 - length3 + length3 },
{ x: pointer.x + length1 / 2, y: pointer.y + length2 - length2 / 2 - length3 },
{ x: pointer.x, y: pointer.y + length2 - length2 / 2 - length3 - (length2 - length3) },
] ]
break break
} }
@ -641,28 +651,28 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
case 16: { case 16: {
points = [ points = [
{ {
x: pointer.x - length1 / 2, x: pointer.x - length1 / 2 + (length1 - length2) / 2,
y: pointer.y + length3 / 2, y: pointer.y + length3 / 2 - (length3 - length4) - length4,
}, },
{ {
x: pointer.x - length1 / 2 + (length1 - length2) / 2, x: pointer.x - length1 / 2 + (length1 - length2) / 2,
y: pointer.y + length3 / 2 - (length3 - length4), y: pointer.y + length3 / 2 - (length3 - length4),
}, },
{ {
x: pointer.x - length1 / 2 + (length1 - length2) / 2, x: pointer.x - length1 / 2,
y: pointer.y + length3 / 2 - (length3 - length4) - length4, y: pointer.y + length3 / 2,
}, },
{ {
x: pointer.x - length1 / 2 + (length1 - length2) / 2 + length2, x: pointer.x - length1 / 2 + length1,
y: pointer.y + length3 / 2 - (length3 - length4) - length4, y: pointer.y + length3 / 2,
}, },
{ {
x: pointer.x - length1 / 2 + (length1 - length2) / 2 + length2, x: pointer.x - length1 / 2 + (length1 - length2) / 2 + length2,
y: pointer.y + length3 / 2 - (length3 - length4) - length4 + length4, y: pointer.y + length3 / 2 - (length3 - length4) - length4 + length4,
}, },
{ {
x: pointer.x - length1 / 2 + length1, x: pointer.x - length1 / 2 + (length1 - length2) / 2 + length2,
y: pointer.y + length3 / 2, y: pointer.y + length3 / 2 - (length3 - length4) - length4,
}, },
] ]
break break
@ -673,25 +683,25 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
const topL = (length1 - length2) / 2 / Math.cos((angle * Math.PI) / 180) // 꺽이는부분 윗쪽 길이 const topL = (length1 - length2) / 2 / Math.cos((angle * Math.PI) / 180) // 꺽이는부분 윗쪽 길이
points = [ points = [
{
x: pointer.x - length1 / 2 + length1,
y: pointer.y + length3 / 2,
},
{ {
x: pointer.x - length1 / 2, x: pointer.x - length1 / 2,
y: pointer.y + length3 / 2, y: pointer.y + length3 / 2,
}, },
{ {
x: pointer.x - length1 / 2 + length4 * Math.cos(degreesToRadians(angle)), x: pointer.x - length1 / 2 + length1,
y: pointer.y + length3 / 2 - length4 * Math.sin(degreesToRadians(angle)), y: pointer.y + length3 / 2,
},
{
x: pointer.x - length1 / 2 + length4 * Math.cos(degreesToRadians(angle)) + length2 + topL * Math.cos(degreesToRadians(angle)),
y: pointer.y + length3 / 2 - length4 * Math.sin(degreesToRadians(angle)) + topL * Math.sin(degreesToRadians(angle)),
}, },
{ {
x: pointer.x - length1 / 2 + length4 * Math.cos(degreesToRadians(angle)) + length2, x: pointer.x - length1 / 2 + length4 * Math.cos(degreesToRadians(angle)) + length2,
y: pointer.y + length3 / 2 - length4 * Math.sin(degreesToRadians(angle)), y: pointer.y + length3 / 2 - length4 * Math.sin(degreesToRadians(angle)),
}, },
{ {
x: pointer.x - length1 / 2 + length4 * Math.cos(degreesToRadians(angle)) + length2 + topL * Math.cos(degreesToRadians(angle)), x: pointer.x - length1 / 2 + length4 * Math.cos(degreesToRadians(angle)),
y: pointer.y + length3 / 2 - length4 * Math.sin(degreesToRadians(angle)) + topL * Math.sin(degreesToRadians(angle)), y: pointer.y + length3 / 2 - length4 * Math.sin(degreesToRadians(angle)),
}, },
] ]
break break
@ -1066,45 +1076,294 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
canvas?.renderAll() canvas?.renderAll()
} }
const updateFlippedPoints = (polygon) => { /**
if (!(polygon instanceof fabric.Polygon)) { * 면형상 작도시 라인 속성 넣는 로직
console.error('The object is not a Polygon.') * 폴리곤으로 보면 직선방향에 따라 아래쪽인지 윗쪽인지 판단이 가능하다고 생각하여
return * south -> 밑면은 무조건 right direction이라 가정하고 작업함 좌우반전시 반대로 그려지는 경우도 생기지만 그럴땐 흐름방향에 따라 최대값(최소값) 찾아
} * 해당 하는 흐름에 맞게 변경함
* @param { } polygon
*/
const { flipX, flipY, width, height, points, left, top, scaleX, scaleY } = polygon //폴리곤, 상하반전, 좌우반전
const changeSurfaceLineType = (polygon) => {
const { isXInversion, isYInversion } = polygon //상하반전, 좌우반전
// 현재 points의 사본 가져오기 polygon.lines.forEach((line) => {
const newPoints = points.map((point) => { line.attributes.type = LINE_TYPE.WALLLINE.GABLE
let x = point.x
let y = point.y
// flipX 적용
if (flipX) {
x = width - x
}
// flipY 적용
if (flipY) {
y = height - y
}
// 스케일 및 전역 좌표 고려
x = (x - width / 2) * scaleX + width / 2
y = (y - height / 2) * scaleY + height / 2
return { x, y }
}) })
// flipX, flipY를 초기화 const directionConfig = {
polygon.flipX = false south: { evaesDirection: 'right', ridgeDirection: 'left', coord1: 'y1', coord2: 'y2' },
polygon.flipY = false north: { evaesDirection: 'left', ridgeDirection: 'right', coord1: 'y1', coord2: 'y2' },
east: { evaesDirection: 'top', ridgeDirection: 'bottom', coord1: 'x1', coord2: 'x2' },
west: { evaesDirection: 'bottom', ridgeDirection: 'top', coord1: 'x1', coord2: 'x2' },
}
// points 업데이트 const { evaesDirection, ridgeDirection, coord1, coord2 } = directionConfig[polygon.direction] || directionConfig.west
polygon.set({ points: newPoints })
polygon.setCoords()
return polygon polygon.lines.forEach((line) => {
if (line[coord1] === line[coord2]) {
if (line.direction === evaesDirection) {
line.attributes.type = LINE_TYPE.WALLLINE.EAVES
} else if (line.direction === ridgeDirection) {
line.attributes.type = LINE_TYPE.SUBLINE.RIDGE
}
}
})
/**
* 진짜 처마 라인인지 확인하는 로직 -> 특정 모양에 따라 처마가 없는 경우가 있는데 위에 로직으로는
* 용마루도 처마로 만들어서 재보정
*/
//직선 찾는 로직
const maxLine = polygon.lines.filter((line) => line[coord1] === line[coord2])
if (maxLine.length > 0) {
const maxLineSorted = maxLine.reduce((a, b) => {
return (polygon.direction === 'south' || polygon.direction === 'east' ? b : a)[coord1] >
(polygon.direction === 'south' || polygon.direction === 'east' ? a : b)[coord1]
? b
: a
})
//정렬된 폴리곤이 아니면(대각선이 존재하는 폴리곤일때)
if (!polygon.isSortedPoints) {
//좌우 반전을 했으면 반대로 정의함
if (isYInversion || isXInversion) {
polygon.lines.forEach((line) => {
if (line.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
line.attributes.type = LINE_TYPE.SUBLINE.RIDGE
} else if (line.attributes.type === LINE_TYPE.SUBLINE.RIDGE) {
line.attributes.type = LINE_TYPE.WALLLINE.EAVES
}
})
}
}
if (maxLine.length === 1) {
const maxLineCoord = polygon.lines.reduce((a, b) => {
return (polygon.direction === 'south' || polygon.direction === 'east' ? b : a)[coord1] >
(polygon.direction === 'south' || polygon.direction === 'east' ? a : b)[coord1]
? b
: a
})
const isRealEavesLine = polygon.lines.filter((line) => line.attributes.type === LINE_TYPE.WALLLINE.EAVES)
if (isRealEavesLine.length > 0) {
isRealEavesLine.forEach((line) => {
if (polygon.direction === 'south' || polygon.direction === 'north') {
const targetCoord =
polygon.direction === 'south' ? Math.max(maxLineCoord.y1, maxLineCoord.y2) : Math.min(maxLineCoord.y1, maxLineCoord.y2)
const realLineCoord = polygon.direction === 'south' ? Math.max(line.y1, line.y2) : Math.min(line.y1, line.y2)
if (targetCoord !== realLineCoord) {
line.attributes.type = LINE_TYPE.SUBLINE.RIDGE
}
} else if (polygon.direction === 'east' || polygon.direction === 'west') {
const targetCoord =
polygon.direction === 'east' ? Math.max(maxLineCoord.x1, maxLineCoord.x2) : Math.min(maxLineCoord.x1, maxLineCoord.x2)
const realLineCoord = polygon.direction === 'east' ? Math.max(line.x1, line.x2) : Math.min(line.x1, line.x2)
if (targetCoord !== realLineCoord) {
line.attributes.type = LINE_TYPE.SUBLINE.RIDGE
}
}
})
}
}
}
}
function findCentroid(points) {
let sumX = 0,
sumY = 0
for (let i = 0; i < points.length; i++) {
sumX += points[i].x
sumY += points[i].y
}
return { x: sumX / points.length, y: sumY / points.length }
}
// 도형의 포인트를 왼쪽부터 반시계 방향으로 정렬하는 함수
/**
* 다각형의 점들을 시계 반대 방향으로 정렬하는 함수
* @param {Array} points - {x, y} 좌표 객체 배열
* @param {Object} startPoint - 시작점 (제공되지 않으면 가장 왼쪽 아래 점을 사용)
* @returns {Array} 시계 반대 방향으로 정렬된 점들의 배열
*/
function orderPointsCounterClockwise(points, startPoint = null) {
if (points.length <= 3) {
return points // 점이 3개 이하면 이미 다각형의 모든 점이므로 그대로 반환
}
// 시작점이 제공되지 않았다면 가장 왼쪽 아래 점을 찾음
let start = startPoint
if (!start) {
start = points[0]
for (let i = 1; i < points.length; i++) {
if (points[i].x < start.x || (points[i].x === start.x && points[i].y < start.y)) {
start = points[i]
}
}
}
// 다각형의 중심점 계산
let centerX = 0,
centerY = 0
for (let i = 0; i < points.length; i++) {
centerX += points[i].x
centerY += points[i].y
}
centerX /= points.length
centerY /= points.length
// 시작점에서 시계 반대 방향으로 각도 계산
let angles = []
for (let i = 0; i < points.length; i++) {
// 시작점은 제외
if (points[i] === start) continue
// 시작점을 기준으로 각 점의 각도 계산
let angle = Math.atan2(points[i].y - start.y, points[i].x - start.x)
// 각도가 음수면 2π를 더해 0~2π 범위로 변환
if (angle < 0) angle += 2 * Math.PI
angles.push({
point: points[i],
angle: angle,
})
}
// 각도에 따라 정렬 (시계 반대 방향)
angles.sort((a, b) => a.angle - b.angle)
// 정렬된 배열 생성 (시작점을 첫 번째로)
let orderedPoints = [start]
for (let i = 0; i < angles.length; i++) {
orderedPoints.push(angles[i].point)
}
return orderedPoints
}
/**
* 특정 점에서 시작하여 시계 반대 방향으로 다음 점을 찾는 함수
* @param {Object} currentPoint - 현재 {x, y}
* @param {Array} points - 모든 점들의 배열
* @param {Array} visited - 방문한 점들의 인덱스 배열
* @param {Object} prevVector - 이전 벡터 방향 ( 호출에서는 null)
* @returns {Object} 다음 점의 인덱스와 객체
*/
function findNextCounterClockwisePoint(currentPoint, points, visited, prevVector = null) {
let minAngle = Infinity
let nextIndex = -1
// 이전 벡터가 없으면 (첫 점인 경우) 아래쪽을 향하는 벡터 사용
if (!prevVector) {
prevVector = { x: 0, y: -1 }
}
for (let i = 0; i < points.length; i++) {
// 이미 방문했거나 현재 점이면 건너뜀
if (visited.includes(i) || (points[i].x === currentPoint.x && points[i].y === currentPoint.y)) {
continue
}
// 현재 점에서 다음 후보 점으로의 벡터
let vector = {
x: points[i].x - currentPoint.x,
y: points[i].y - currentPoint.y,
}
// 벡터의 크기
let magnitude = Math.sqrt(vector.x * vector.x + vector.y * vector.y)
// 단위 벡터로 정규화
vector.x /= magnitude
vector.y /= magnitude
// 이전 벡터와 현재 벡터 사이의 각도 계산 (내적 사용)
let dotProduct = prevVector.x * vector.x + prevVector.y * vector.y
let crossProduct = prevVector.x * vector.y - prevVector.y * vector.x
// 각도 계산 (atan2 사용)
let angle = Math.atan2(crossProduct, dotProduct)
// 시계 반대 방향으로 가장 작은 각도를 가진 점 찾기
// 각도가 음수면 2π를 더해 0~2π 범위로 변환
if (angle < 0) angle += 2 * Math.PI
if (angle < minAngle) {
minAngle = angle
nextIndex = i
}
}
return nextIndex !== -1 ? { index: nextIndex, point: points[nextIndex] } : null
}
/**
* 다각형의 점들을 시계 반대 방향으로 추적하는 함수
* @param {Array} points - {x, y} 좌표 객체 배열
* @param {Object} startPoint - 시작점 (제공되지 않으면 가장 왼쪽 아래 점을 사용)
* @returns {Array} 시계 반대 방향으로 정렬된 점들의 배열
*/
function tracePolygonCounterClockwise(points, startPoint = null) {
if (points.length <= 3) {
return orderPointsCounterClockwise(points, startPoint)
}
// 시작점이 제공되지 않았다면 가장 왼쪽 아래 점을 찾음
let startIndex = 0
if (!startPoint) {
for (let i = 1; i < points.length; i++) {
if (points[i].x < points[startIndex].x || (points[i].x === points[startIndex].x && points[i].y < points[startIndex].y)) {
startIndex = i
}
}
startPoint = points[startIndex]
} else {
// 시작점이 제공된 경우 해당 점의 인덱스 찾기
for (let i = 0; i < points.length; i++) {
if (points[i].x === startPoint.x && points[i].y === startPoint.y) {
startIndex = i
break
}
}
}
// 결과 배열 초기화
let orderedPoints = [startPoint]
let visited = [startIndex]
let currentPoint = startPoint
let prevVector = null
// 모든 점을 방문할 때까지 반복
while (visited.length < points.length) {
let next = findNextCounterClockwisePoint(currentPoint, points, visited, prevVector)
if (!next) break // 더 이상 찾을 점이 없으면 종료
orderedPoints.push(next.point)
visited.push(next.index)
// 이전 벡터 업데이트 (현재 점에서 다음 점으로의 벡터)
prevVector = {
x: next.point.x - currentPoint.x,
y: next.point.y - currentPoint.y,
}
// 벡터 정규화
let magnitude = Math.sqrt(prevVector.x * prevVector.x + prevVector.y * prevVector.y)
prevVector.x /= magnitude
prevVector.y /= magnitude
currentPoint = next.point
}
return orderedPoints
} }
return { return {
@ -1115,5 +1374,6 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
changeSurfaceLinePropertyEvent, changeSurfaceLinePropertyEvent,
changeSurfaceLineProperty, changeSurfaceLineProperty,
changeSurfaceLinePropertyReset, changeSurfaceLinePropertyReset,
changeSurfaceLineType,
} }
} }

View File

@ -81,9 +81,9 @@ export function useContextMenu() {
switch (selectedMenu) { switch (selectedMenu) {
case 'outline': case 'outline':
break break
default: // default:
setContextMenu([]) // setContextMenu([])
break // break
} }
} }

View File

@ -79,6 +79,9 @@ export function useEvent() {
// 마우스 위치 기준으로 확대/축소 // 마우스 위치 기준으로 확대/축소
canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom) canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom)
canvas.requestRenderAll()
canvas.calcOffset()
// 이벤트의 기본 동작 방지 (스크롤 방지) // 이벤트의 기본 동작 방지 (스크롤 방지)
opt.e.preventDefault() opt.e.preventDefault()
opt.e.stopPropagation() opt.e.stopPropagation()

View File

@ -414,7 +414,7 @@ export function usePlan(params = {}) {
useEffect(() => { useEffect(() => {
setSelectedPlan(currentCanvasPlan) setSelectedPlan(currentCanvasPlan)
handleCurrentPlanUrl() handleCurrentPlanUrl()
resetCurrentObject() // resetCurrentObject()
resetModuleSetupSurface() resetModuleSetupSurface()
}, [currentCanvasPlan]) }, [currentCanvasPlan])

View File

@ -176,6 +176,10 @@ export const usePolygon = () => {
* @param showDirectionText * @param showDirectionText
*/ */
const drawDirectionArrow = (polygon, showDirectionText = true) => { const drawDirectionArrow = (polygon, showDirectionText = true) => {
if (!polygon) {
return
}
if (polygon.points.length < 3) { if (polygon.points.length < 3) {
return return
} }
@ -767,7 +771,7 @@ export const usePolygon = () => {
obj.type === 'QLine' && obj.type === 'QLine' &&
obj.attributes?.type !== 'pitchSizeLine' && obj.attributes?.type !== 'pitchSizeLine' &&
obj.attributes?.roofId === polygon.id && obj.attributes?.roofId === polygon.id &&
(innerLineTypes.includes(obj.name) || !obj.name), innerLineTypes.includes(obj.name),
) )
innerLines = [...polygon.innerLines] innerLines = [...polygon.innerLines]

View File

@ -37,7 +37,7 @@
"modal.roof.shape.setting.patten.a": "Aパターン", "modal.roof.shape.setting.patten.a": "Aパターン",
"modal.roof.shape.setting.patten.b": "Bパターン", "modal.roof.shape.setting.patten.b": "Bパターン",
"modal.roof.shape.setting.side": "別に設定", "modal.roof.shape.setting.side": "別に設定",
"plan.menu.roof.cover": "屋根作図", "plan.menu.roof.cover": "伏せ図入力",
"plan.menu.roof.cover.outline.drawing": "外壁線を描く", "plan.menu.roof.cover.outline.drawing": "外壁線を描く",
"plan.menu.roof.cover.roof.shape.setting": "屋根形状の設定", "plan.menu.roof.cover.roof.shape.setting": "屋根形状の設定",
"plan.menu.roof.cover.roof.shape.passivity.setting": "屋根形状の手動設定", "plan.menu.roof.cover.roof.shape.passivity.setting": "屋根形状の手動設定",
@ -72,7 +72,7 @@
"common.setting.rollback": "前に戻る", "common.setting.rollback": "前に戻る",
"modal.cover.outline.remove": "外壁の取り外し", "modal.cover.outline.remove": "外壁の取り外し",
"modal.cover.outline.select.move": "外壁選択の移動", "modal.cover.outline.select.move": "外壁選択の移動",
"plan.menu.placement.surface": "配置面", "plan.menu.placement.surface": "実測値入力",
"plan.menu.placement.surface.slope.setting": "傾斜設定", "plan.menu.placement.surface.slope.setting": "傾斜設定",
"plan.menu.placement.surface.drawing": "配置面の描画", "plan.menu.placement.surface.drawing": "配置面の描画",
"modal.placement.surface.drawing.straight.line": "直線", "modal.placement.surface.drawing.straight.line": "直線",
@ -128,7 +128,7 @@
"modal.module.basic.setting.pitch.module.row.margin": "上下間隔", "modal.module.basic.setting.pitch.module.row.margin": "上下間隔",
"modal.module.basic.setting.pitch.module.column.amount": "列数", "modal.module.basic.setting.pitch.module.column.amount": "列数",
"modal.module.basic.setting.pitch.module.column.margin": "左右間隔", "modal.module.basic.setting.pitch.module.column.margin": "左右間隔",
"modal.module.basic.setting.prev": "移転", "modal.module.basic.setting.prev": "前に戻る",
"modal.module.basic.setting.passivity.placement": "手動配置", "modal.module.basic.setting.passivity.placement": "手動配置",
"modal.module.basic.setting.auto.placement": "設定値に自動配置", "modal.module.basic.setting.auto.placement": "設定値に自動配置",
"plan.menu.module.circuit.setting.circuit.trestle.setting": "回路設定", "plan.menu.module.circuit.setting.circuit.trestle.setting": "回路設定",
@ -178,7 +178,7 @@
"modal.roof.alloc.select.parallel": "筋配置", "modal.roof.alloc.select.parallel": "筋配置",
"modal.roof.alloc.select.stairs": "千鳥配置", "modal.roof.alloc.select.stairs": "千鳥配置",
"modal.roof.alloc.apply": "選択した屋根材として割り当て", "modal.roof.alloc.apply": "選択した屋根材として割り当て",
"plan.menu.estimate.docDown": "各種資料ダウンロード", "plan.menu.estimate.docDownload": "見積書出力",
"plan.menu.estimate.save": "保存", "plan.menu.estimate.save": "保存",
"plan.menu.estimate.reset": "初期化", "plan.menu.estimate.reset": "初期化",
"plan.menu.estimate.copy": "見積書のコピー", "plan.menu.estimate.copy": "見積書のコピー",
@ -558,7 +558,7 @@
"board.faq.title": "FAQ", "board.faq.title": "FAQ",
"board.faq.sub.title": "FAQリスト", "board.faq.sub.title": "FAQリスト",
"board.archive.title": "各種資料ダウンロード", "board.archive.title": "各種資料ダウンロード",
"board.archive.sub.title": "見積書一覧", "board.archive.sub.title": "掲載資料一覧",
"board.list.header.rownum": "番号", "board.list.header.rownum": "番号",
"board.list.header.title": "タイトル", "board.list.header.title": "タイトル",
"board.list.header.regDt": "登録日", "board.list.header.regDt": "登録日",
@ -594,6 +594,7 @@
"myinfo.message.password.error": "パスワードが間違っています。", "myinfo.message.password.error": "パスワードが間違っています。",
"login": "ログイン", "login": "ログイン",
"login.auto.page.text": "自動ログイン中です。", "login.auto.page.text": "自動ログイン中です。",
"login.fail": "アカウントが存在しないか、パスワードが間違っています。",
"login.id.save": "ID保存", "login.id.save": "ID保存",
"login.id.placeholder": "IDを入力してください。", "login.id.placeholder": "IDを入力してください。",
"login.password.placeholder": "パスワードを入力してください。", "login.password.placeholder": "パスワードを入力してください。",
@ -886,7 +887,7 @@
"estimate.detail.drawingEstimateCreateDate": "登録日", "estimate.detail.drawingEstimateCreateDate": "登録日",
"estimate.detail.lastEditDatetime": "変更日時", "estimate.detail.lastEditDatetime": "変更日時",
"estimate.detail.saleStoreId": "一次販売店名", "estimate.detail.saleStoreId": "一次販売店名",
"estimate.detail.estimateDate": "見積日", "estimate.detail.estimateDate": "見積作成日",
"estimate.detail.otherSaleStoreId": "二次販売店名", "estimate.detail.otherSaleStoreId": "二次販売店名",
"estimate.detail.noOtherSaleStoreId": "二次店なし", "estimate.detail.noOtherSaleStoreId": "二次店なし",
"estimate.detail.receiveUser": "担当者", "estimate.detail.receiveUser": "担当者",
@ -957,6 +958,7 @@
"estimate.detail.estimateCopyPopup.close": "閉じる", "estimate.detail.estimateCopyPopup.close": "閉じる",
"estimate.detail.estimateCopyPopup.copyBtn": "見積コピー", "estimate.detail.estimateCopyPopup.copyBtn": "見積コピー",
"estimate.detail.estimateCopyPopup.copy.alertMessage": "見積書がコピーされました。コピーした見積情報に移動します。", "estimate.detail.estimateCopyPopup.copy.alertMessage": "見積書がコピーされました。コピーした見積情報に移動します。",
"estimate.detail.estimateCopyPopup.copy.alertMessageError": "キャンバスのコピー中にエラーが発生しました.",
"estimate.detail.productFeaturesPopup.title": "製品特異事項", "estimate.detail.productFeaturesPopup.title": "製品特異事項",
"estimate.detail.productFeaturesPopup.close": "閉じる", "estimate.detail.productFeaturesPopup.close": "閉じる",
"estimate.detail.productFeaturesPopup.requiredStoreId": "一次販売店は必須です。", "estimate.detail.productFeaturesPopup.requiredStoreId": "一次販売店は必須です。",
@ -1034,5 +1036,10 @@
"roof.exceed.count": "屋根材は4つまで選択可能です。", "roof.exceed.count": "屋根材は4つまで選択可能です。",
"outerLine.property.fix": "外壁線の属性設定 を完了しますか?", "outerLine.property.fix": "外壁線の属性設定 を完了しますか?",
"outerLine.property.close": "外壁線の属性設定 を終了しますか?", "outerLine.property.close": "外壁線の属性設定 を終了しますか?",
"want.to.complete.auxiliary.creation": "보補助線の作成を完了しますか?" "want.to.complete.auxiliary.creation": "補助線の作成を完了しますか?",
"modal.placement.initial.setting.plan.drawing.only.number": "(※数字は[半角]入力のみ可能です。)",
"wall.line.not.found": "外壁がありません",
"roof.line.not.found": "屋根形状がありません",
"roof.material.can.not.delete": "割り当てられた配置面があります。",
"chidory.can.not.install" : "千鳥配置できない工法です。"
} }

View File

@ -178,7 +178,7 @@
"modal.roof.alloc.select.parallel": "병렬식", "modal.roof.alloc.select.parallel": "병렬식",
"modal.roof.alloc.select.stairs": "계단식", "modal.roof.alloc.select.stairs": "계단식",
"modal.roof.alloc.apply": "선택한 지붕재로 할당", "modal.roof.alloc.apply": "선택한 지붕재로 할당",
"plan.menu.estimate.docDown": "문서 다운로드", "plan.menu.estimate.docDownload": "문서 다운로드",
"plan.menu.estimate.save": "저장", "plan.menu.estimate.save": "저장",
"plan.menu.estimate.reset": "초기화", "plan.menu.estimate.reset": "초기화",
"plan.menu.estimate.copy": "견적서 복사", "plan.menu.estimate.copy": "견적서 복사",
@ -594,6 +594,7 @@
"myinfo.message.password.error": "비밀번호가 틀렸습니다.", "myinfo.message.password.error": "비밀번호가 틀렸습니다.",
"login": "로그인", "login": "로그인",
"login.auto.page.text": "자동로그인 중 입니다.", "login.auto.page.text": "자동로그인 중 입니다.",
"login.fail": "계정이 없거나 비밀번호가 잘못되었습니다.",
"login.id.save": "ID Save", "login.id.save": "ID Save",
"login.id.placeholder": "아이디를 입력해주세요.", "login.id.placeholder": "아이디를 입력해주세요.",
"login.password.placeholder": "비밀번호를 입력해주세요.", "login.password.placeholder": "비밀번호를 입력해주세요.",
@ -886,7 +887,7 @@
"estimate.detail.drawingEstimateCreateDate": "등록일", "estimate.detail.drawingEstimateCreateDate": "등록일",
"estimate.detail.lastEditDatetime": "변경일시", "estimate.detail.lastEditDatetime": "변경일시",
"estimate.detail.saleStoreId": "1차 판매점명", "estimate.detail.saleStoreId": "1차 판매점명",
"estimate.detail.estimateDate": "견적일", "estimate.detail.estimateDate": "견적작성일",
"estimate.detail.otherSaleStoreId": "2차 판매점명", "estimate.detail.otherSaleStoreId": "2차 판매점명",
"estimate.detail.noOtherSaleStoreId": "2차점 없음", "estimate.detail.noOtherSaleStoreId": "2차점 없음",
"estimate.detail.receiveUser": "담당자", "estimate.detail.receiveUser": "담당자",
@ -957,6 +958,7 @@
"estimate.detail.estimateCopyPopup.close": "닫기", "estimate.detail.estimateCopyPopup.close": "닫기",
"estimate.detail.estimateCopyPopup.copyBtn": "견적복사", "estimate.detail.estimateCopyPopup.copyBtn": "견적복사",
"estimate.detail.estimateCopyPopup.copy.alertMessage": "견적서가 복사되었습니다. 복사된 물건정보로 이동합니다.", "estimate.detail.estimateCopyPopup.copy.alertMessage": "견적서가 복사되었습니다. 복사된 물건정보로 이동합니다.",
"estimate.detail.estimateCopyPopup.copy.alertMessageError": "캔버스 복사 중 오류 발생.",
"estimate.detail.productFeaturesPopup.title": "제품특이사항", "estimate.detail.productFeaturesPopup.title": "제품특이사항",
"estimate.detail.productFeaturesPopup.close": "닫기", "estimate.detail.productFeaturesPopup.close": "닫기",
"estimate.detail.productFeaturesPopup.requiredStoreId": "1차 판매점은 필수값 입니다.", "estimate.detail.productFeaturesPopup.requiredStoreId": "1차 판매점은 필수값 입니다.",
@ -1034,5 +1036,10 @@
"roof.exceed.count": "지붕재는 4개까지 선택 가능합니다.", "roof.exceed.count": "지붕재는 4개까지 선택 가능합니다.",
"outerLine.property.fix": "외벽선 속성 설정을 완료하시겠습니까?", "outerLine.property.fix": "외벽선 속성 설정을 완료하시겠습니까?",
"outerLine.property.close": "외벽선 속성 설정을 종료하시겠습니까?", "outerLine.property.close": "외벽선 속성 설정을 종료하시겠습니까?",
"want.to.complete.auxiliary.creation": "보조선 작성을 완료하시겠습니까?" "want.to.complete.auxiliary.creation": "보조선 작성을 완료하시겠습니까?",
"modal.placement.initial.setting.plan.drawing.only.number": "(※ 숫자는 [반각]입력만 가능합니다.)",
"wall.line.not.found": "외벽선이 없습니다.",
"roof.line.not.found": "지붕형상이 없습니다.",
"roof.material.can.not.delete": "할당된 배치면이 있습니다.",
"chidory.can.not.install" : "치조 불가 공법입니다."
} }

6
src/store/hotkeyAtom.js Normal file
View File

@ -0,0 +1,6 @@
import { atom } from 'recoil'
export const hotkeyStore = atom({
key: 'hotkeyState',
default: [],
})

View File

@ -348,15 +348,15 @@ export const calculateIntersection = (line1, line2) => {
} }
// Determine the min and max for line1 and line2 for both x and y // Determine the min and max for line1 and line2 for both x and y
const line1MinX = Math.min(line1.x1, line1.x2) const line1MinX = Math.min(line1.x1, line1.x2) - 5
const line1MaxX = Math.max(line1.x1, line1.x2) const line1MaxX = Math.max(line1.x1, line1.x2) + 5
const line2MinX = Math.min(line2.x1, line2.x2) const line2MinX = Math.min(line2.x1, line2.x2) - 5
const line2MaxX = Math.max(line2.x1, line2.x2) const line2MaxX = Math.max(line2.x1, line2.x2) + 5
const line1MinY = Math.min(line1.y1, line1.y2) const line1MinY = Math.min(line1.y1, line1.y2) - 5
const line1MaxY = Math.max(line1.y1, line1.y2) const line1MaxY = Math.max(line1.y1, line1.y2) + 5
const line2MinY = Math.min(line2.y1, line2.y2) const line2MinY = Math.min(line2.y1, line2.y2) - 5
const line2MaxY = Math.max(line2.y1, line2.y2) const line2MaxY = Math.max(line2.y1, line2.y2) + 5
// Check if the intersection X and Y are within the range of both lines // Check if the intersection X and Y are within the range of both lines
if ( if (
@ -518,14 +518,23 @@ export const sortedPointLessEightPoint = (points) => {
*/ */
// 직선의 방정식. // 직선의 방정식.
// 방정식은 ax + by + c = 0이며, 점의 좌표를 대입하여 계산된 값은 직선과 점 사이의 관계를 나타낸다. // 방정식은 ax + by + c = 0이며, 점의 좌표를 대입하여 계산된 값은 직선과 점 사이의 관계를 나타낸다.
export function isPointOnLine(line, point) { export function isPointOnLine({ x1, y1, x2, y2 }, { x, y }) {
const a = line.y2 - line.y1 /*const a = line.y2 - line.y1
const b = line.x1 - line.x2 const b = line.x1 - line.x2
const c = line.x2 * line.y1 - line.x1 * line.y2 const c = line.x2 * line.y1 - line.x1 * line.y2
const result = Math.abs(a * point.x + b * point.y + c) / 100 const result = Math.abs(a * point.x + b * point.y + c) / 100
// 점이 선 위에 있는지 확인 // 점이 선 위에 있는지 확인
return result <= 10 return result <= 10*/
// 직선 방정식 만족 여부 확인
const crossProduct = (y - y1) * (x2 - x1) - (x - x1) * (y2 - y1)
if (Math.abs(crossProduct) > 5) return false // 작은 오차 허용
// 점이 선분의 범위 내에 있는지 확인
const withinXRange = Math.min(x1, x2) <= x && x <= Math.max(x1, x2)
const withinYRange = Math.min(y1, y2) <= y && y <= Math.max(y1, y2)
return withinXRange && withinYRange
} }
/** /**
* 점과 가까운 line 찾기 * 점과 가까운 line 찾기

View File

@ -305,6 +305,9 @@ export function removeDuplicatePolygons(polygons) {
} }
export const isSamePoint = (a, b) => { export const isSamePoint = (a, b) => {
if (!a || !b) {
return false
}
return Math.abs(Math.round(a.x) - Math.round(b.x)) <= 2 && Math.abs(Math.round(a.y) - Math.round(b.y)) <= 2 return Math.abs(Math.round(a.x) - Math.round(b.x)) <= 2 && Math.abs(Math.round(a.y) - Math.round(b.y)) <= 2
} }