Compare commits

...

121 Commits

Author SHA1 Message Date
51e1f70c24 Merge pull request '견적서 저장시 체크' (#21) from feature/qcast-886 into dev
Reviewed-on: #21
2025-04-15 18:08:55 +09:00
3c14f5de52 견적서 저장시 체크 2025-04-15 18:08:05 +09:00
8f4e53c759 #982 표시 문자 변경 2025-04-14 18:14:49 +09:00
a1871e105f Merge pull request '975- 아이템(양단케이블) 타입 코드 추가에 따른 아이템 목록 수정' (#20) from feature/ysCha into dev
Reviewed-on: #20
2025-04-14 15:03:34 +09:00
ed69943dc6 975- 아이템(양단케이블) 타입 코드 추가에 따른 아이템 목록 수정 2025-04-14 15:01:18 +09:00
61f1036a24 변별로 설정 - 한쪽흐름 출폭 적용 추가 2025-04-11 10:58:04 +09:00
97f0749f5c Merge pull request '견적서 오픈플래그 텍스트' (#19) from feature/qcast-886 into dev
Reviewed-on: #19
2025-04-10 10:46:57 +09:00
82210a99c9 견적서 오픈플래그 텍스트 2025-04-10 10:45:36 +09:00
546133c27e 오류 문구 수정 2025-04-09 17:29:37 +09:00
8662a37177 # 974 - 지붕형상 설정 전 팝업 제거 2025-04-09 17:22:55 +09:00
11cf3b8403 chore: 내부 개발 서버 기동을 위한 실행 파일 추가
- 추후에 제거 예정
2025-04-09 17:18:58 +09:00
62517870b7 Merge pull request 'QCast Front #886 견적서 OPEN 제품 단가필드에 OPEN 텍스트 추가' (#18) from feature/qcast-886 into dev
Reviewed-on: #18
2025-04-08 16:33:41 +09:00
7ff52a7b19 QCast Front #886 견적서 OPEN 제품 단가필드에 OPEN 텍스트 추가 2025-04-08 16:33:06 +09:00
2e86c793c3 Merge pull request '955-견적서 2차 SAP 판매점 노출' (#17) from feature/ysCha into dev
Reviewed-on: #17
2025-04-04 16:39:05 +09:00
d38acba7c9 955-견적서 2차 SAP 판매점 노출 2025-04-04 16:34:09 +09:00
d386c42c08 Merge branch 'qcast-pub' into dev 2025-04-02 16:39:19 +09:00
ee6f62b4ba chore: nextjs hotfix 로 버전 수정 2025-04-02 16:32:19 +09:00
de8ba00e55 chore: Update startscript to run development server on port 5000 2025-04-02 16:17:52 +09:00
b7cbc3ec47 test: deploy test 2025-04-02 16:00:55 +09:00
김민식
4b6f0b1b06 plan tab 좌측 objectNo 영역 추가 2025-04-02 13:54:44 +09:00
김민식
9df16cad02 모듈 열/단 버퍼 추가 2025-04-02 13:54:06 +09:00
김민식
8354508a2b 다국어 추가 2025-04-02 13:53:34 +09:00
김민식
ced0eb9fc6 🚨chore: Sync Sass 2025-04-02 13:53:24 +09:00
655fef744c 모듈 선택 표시 2025-04-02 11:12:23 +09:00
0a5b6ce132 test: deploy test 2025-04-01 17:59:57 +09:00
66f1293b95 Merge pull request '955-견적서 상품검색할때 이미지명(형명)도 같이 표시' (#15) from feature/ysCha into dev
Reviewed-on: #15
2025-04-01 17:14:43 +09:00
167740f33d 955-견적서 상품검색할때 이미지명(형명)도 같이 표시 2025-04-01 17:12:02 +09:00
be54df76f7 통합테스트(Integration Test) #956 이동 기능 수정 2025-04-01 13:19:12 +09:00
c2fbc83485 Merge branch 'dev' into feature/dev-yj 2025-04-01 13:07:31 +09:00
d10642d6d5 메뉴에서 안쓰는 내용 제거 2025-03-31 13:50:06 +09:00
3509e5fbb6 Update startscript to run development server on port 5000 2025-03-28 17:13:46 +09:00
a5fb44ad36 Merge branch 'dev' into feature/dev-yj 2025-03-28 15:25:20 +09:00
c88a07a227 모듈 남쪽설치 치조시 좌측으로 붙는 오류 수정 2025-03-28 15:25:03 +09:00
5982a2aa10 Merge pull request '그리드 테이블 경계표시 추가' (#14) from feature/ysCha into dev
Reviewed-on: #14
2025-03-28 15:15:40 +09:00
3fd81e771d 그리드 테이블 경계표시 추가 2025-03-28 15:13:05 +09:00
c9a53b7159 Merge pull request '#944 물건번호 생성시 시작번호 R->S 변경에따른 주석정리' (#13) from feature/qcast-944 into dev
Reviewed-on: #13
2025-03-28 13:29:13 +09:00
24ba8bfb14 #944 물건번호 생성시 시작번호 R->S 변경에따른 주석정리 2025-03-28 13:27:23 +09:00
b82901271d Merge pull request 'HANASYS 개선건 #950' (#12) from feature/qcast-950 into dev
Reviewed-on: #12
2025-03-28 12:38:58 +09:00
25957a37e1 HANASYS 개선건 #950 2025-03-28 12:37:54 +09:00
83f0eef9eb Merge pull request 'HANASYS 개선건 #947' (#11) from feature/qcast-947 into dev
Reviewed-on: #11
2025-03-28 12:29:13 +09:00
46f90aff1f HANASYS 개선건 #947 2025-03-28 12:27:59 +09:00
11f69a01bb 번역 수정 2025-03-28 11:15:12 +09:00
c8f70e0746 번역 수정 2025-03-28 10:58:34 +09:00
de5901492b 기존 저장된 지붕재 index 수정 2025-03-27 15:17:42 +09:00
6f5b70342d - pcs 모델 추가 버그 수정 2025-03-27 14:41:29 +09:00
7b1c9b681e Merge pull request '견적서::주문분류에서 이미등록된 YJSS 노출' (#10) from feature/ysCha into dev
Reviewed-on: #10
2025-03-27 13:41:25 +09:00
25e8dcc050 견적서::주문분류에서 이미등록된 YJSS 노출 2025-03-27 13:38:15 +09:00
3027f47d5d chore: nextjs 버전 fix & pm2 실행 스크립트 수정 2025-03-27 13:33:29 +09:00
1f723c9ce7 fix: 임포트 구문 추가 2025-03-27 11:05:30 +09:00
5e9c22a928 Merge pull request '📌 feat: Implement usePlan hook for managing floor plan state and interactions, including canvas data handling, plan creation, deletion, and context management.' (#9) from feature/plan-add-dont-dbclick into dev
Reviewed-on: #9
2025-03-27 10:37:14 +09:00
54d06f7d51 가로, 세로 선 없을 경우 return 2025-03-27 10:34:40 +09:00
7f402d5b45 수동 설정 시 text 밖으로 수정 2025-03-27 10:34:15 +09:00
2473cfac17 📌 feat: Implement usePlan hook for managing floor plan state and interactions, including canvas data handling, plan creation, deletion, and context management. 2025-03-27 10:33:34 +09:00
e293d5dfde Merge pull request 'feature/ysCha' (#8) from feature/ysCha into dev
Reviewed-on: #8
2025-03-27 09:58:44 +09:00
cf9acde872 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha
# Conflicts:
#	src/locales/ja.json
2025-03-26 17:00:11 +09:00
93b645e9e8 지붕면 할당 전 제거 시 오류 수정 2025-03-26 15:08:37 +09:00
25778a099f 팝업 닫고 시작 2025-03-26 11:10:42 +09:00
ba9a49501c 지붕면 할당 => 마지막으로 추가된 지붕재 자동 선택 2025-03-26 10:59:29 +09:00
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
cha
82e3527432 견적서::주문분류에서 storePriceList의 pkgRank의 값이 없으면 YJSS 선택불가, 기등록은 변경 가능 2025-03-24 13:27:30 +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
cha
b136bc213c Merge branch 'dev' of https://git.jetbrains.space/nalpari/q-cast-iii/qcast-front into feature/ysCha 2025-03-19 10:00:40 +09:00
84e8af50b8 QSelectbox key 중복 제거 2025-03-18 15:55:11 +09:00
11438773a1 견적서 상세 헤더메뉴 문서다운로드 버튼 명 수정 2025-03-18 13:28:58 +09:00
2e762537fc #918 견적서 상세화면 견적일 -> 견적작성일 메세지 변경 2025-03-18 13:24:58 +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
김민식
16423de079 배치면 초기설정 다국어 적용 2025-03-17 15:44:12 +09:00
김민식
e99a1a3854 배치면 초기설정 화면 다국어 추가 2025-03-17 15:43:57 +09:00
김민식
8ca01757c9 🚨chore: Sync Sass 2025-03-17 15:43:37 +09:00
55 changed files with 1292 additions and 581 deletions

View File

@ -34,3 +34,5 @@ You can check out [the Next.js GitHub repository](https://github.com/vercel/next
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
deploy test

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": {
"dev": "next dev",
"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",
"lint": "next lint",
"serve": "node server.js"
@ -23,7 +24,7 @@
"js-cookie": "^3.0.5",
"mathjs": "^13.0.2",
"mssql": "^11.0.1",
"next": "14.2.21",
"next": "14.2.26",
"next-international": "^1.2.4",
"react": "^18",
"react-chartjs-2": "^5.2.0",

View File

@ -0,0 +1,3 @@
<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 0L15.7942 13.5H0.205771L8 0Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 160 B

View File

@ -203,6 +203,7 @@ export const SAVE_KEY = [
'fontWeight',
'dormerAttributes',
'toFixed',
'isSortedPoints',
]
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 { 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'
export default function AutoLoginPage() {
const [isLoading, setIsLoading] = useState(true)
export default function AutoLoginPage({ autoLoginParam }) {
const router = useRouter()
const [isLoading, setIsLoading] = useState(autoLoginParam === 'Y' ? false : true)
const [globalLocaleState, setGlbalLocaleState] = useRecoilState(globalLocaleStore)
const { promisePost } = useAxios(globalLocaleState)
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 (
<>
{isLoading && <GlobalSpinner />}
<div className="login-input-frame">
<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" style={{ fontWeight: 'bolder' }}>
{getMessage('login.auto.page.text')}
{autoLoginParam !== 'Y' ? (
<>
<div className="login-input-frame">
<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" style={{ fontWeight: 'bolder' }}>
{getMessage('login.auto.page.text')}
</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(() => {
if (autoLoginParam) {
autoLoginProcess(autoLoginParam)
if (autoLoginParam !== 'Y') {
autoLoginProcess(autoLoginParam)
}
}
// console.log('🚀 ~ checkSession ~ checkSession():', checkSession())
@ -334,7 +336,7 @@ export default function Login() {
</div>
</>
)}
{autoLoginParam && <AutoLogin />}
{autoLoginParam && <AutoLogin autoLoginParam={autoLoginParam} />}
</div>
<div className="login-copyright">COPYRIGHT©2024 Hanwha Japan All Rights Reserved.</div>
</div>

View File

@ -6,29 +6,19 @@ import { contextMenuListState, contextMenuState } from '@/store/contextMenu'
import { useTempGrid } from '@/hooks/useTempGrid'
import { useContextMenu } from '@/hooks/useContextMenu'
import { useEvent } from '@/hooks/useEvent'
import { canvasState } from '@/store/canvasAtom'
import { canvasState, currentObjectState } from '@/store/canvasAtom'
export default function QContextMenu(props) {
const canvas = useRecoilValue(canvasState)
const { contextRef, canvasProps } = props
const [contextMenu, setContextMenu] = useRecoilState(contextMenuState)
const contextMenuList = useRecoilValue(contextMenuListState)
const activeObject = canvasProps?.getActiveObject() //
const currentObject = useRecoilValue(currentObjectState)
const { tempGridMode, setTempGridMode } = useTempGrid()
const { handleKeyup } = useContextMenu()
const { addDocumentEventListener, removeDocumentEvent } = useEvent()
// 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 contextLength = contextMenuList.reduce((acc, cur, index) => {
return acc + cur.length
@ -36,11 +26,13 @@ export default function QContextMenu(props) {
return e?.clientY - (contextLength * 25 + contextMenuList.length * 2 * 17)
}
useEffect(() => {
if (!contextRef.current) return
const handleContextMenu = (e) => {
// 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
const position = {
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) })
addDocumentEventListener('keyup', document, handleKeyup)
canvasProps?.upperCanvasEl.removeEventListener('contextmenu', handleContextMenu) //
}
}
const handleClick = (e) => {
// e.preventDefault()
const handleClick = (e) => {
// e.preventDefault()
setContextMenu({ ...contextMenu, visible: false })
}
const handleOutsideClick = (e) => {
// e.preventDefault()
if (contextMenu.visible) {
setContextMenu({ ...contextMenu, visible: false })
removeDocumentEvent('keyup')
}
}
const handleOutsideClick = (e) => {
// e.preventDefault()
if (contextMenu.visible) {
setContextMenu({ ...contextMenu, visible: false })
removeDocumentEvent('keyup')
}
}
useEffect(() => {
if (!contextRef.current) return
canvasProps?.upperCanvasEl.addEventListener('contextmenu', handleContextMenu)
document.addEventListener('click', handleClick)
@ -72,43 +67,9 @@ export default function QContextMenu(props) {
removeDocumentEvent('keyup')
document.removeEventListener('click', handleClick)
document.removeEventListener('click', handleOutsideClick)
canvasProps?.upperCanvasEl.removeEventListener('contextmenu', handleContextMenu) //
}
}, [contextRef, contextMenuList])
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)
})
}
}, [contextRef, contextMenuList, currentObject])
return (
<>

View File

@ -87,7 +87,7 @@ export default function QSelectBox({
<ul className="select-item-wrap">
{options?.length > 0 &&
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>
</li>
))}

View File

@ -13,7 +13,7 @@ import dayjs from 'dayjs'
import { useCommonCode } from '@/hooks/common/useCommonCode'
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
import { SessionContext } from '@/app/SessionProvider'
import Select from 'react-select'
import Select, { components } from 'react-select'
import { convertNumberToPriceDecimal, convertNumberToPriceDecimalToFixed } from '@/util/common-utils'
import ProductFeaturesPop from './popup/ProductFeaturesPop'
import { v4 as uuidv4 } from 'uuid'
@ -175,7 +175,10 @@ export default function Estimate({}) {
row.check = false
estimateOption.map((row2) => {
if (row.pkgYn === '0') {
if (row2 === row.code) {
// if (row2 === row.code) {
// row.check = true
// }
if (row.code.split('、').includes(row2)) {
row.check = true
}
} else {
@ -217,7 +220,10 @@ export default function Estimate({}) {
row.check = false
estimateOption.map((row2) => {
if (row.pkgYn === '0') {
if (row2 === row.code) {
// if (row2 === row.code) {
// row.check = true
// }
if (row.code.split('、').includes(row2)) {
row.check = true
}
} else {
@ -240,7 +246,6 @@ export default function Estimate({}) {
}
}
})
setSpecialNoteList(res)
setSpecialNoteFirstFlg(true)
@ -377,8 +382,8 @@ export default function Estimate({}) {
useEffect(() => {
if (estimateContextState.estimateType !== '') {
const param = {
saleStoreId: session.storeId,
sapSalesStoreCd: session.custCd,
saleStoreId: estimateContextState.sapSaleStoreId,
sapSalesStoreCd: estimateContextState.sapSalesStoreCd,
docTpCd: estimateContextState?.estimateType,
}
@ -387,6 +392,8 @@ export default function Estimate({}) {
if (isNotEmptyArray(res?.data)) {
setStorePriceList(res.data)
}
setItemChangeYn(true)
})
if (estimateContextState.estimateType === 'YJSS') {
@ -416,8 +423,6 @@ export default function Estimate({}) {
handlePricing('UNIT_PRICE')
}
}
setItemChangeYn(true)
}
}, [estimateContextState?.estimateType])
@ -469,6 +474,21 @@ export default function Estimate({}) {
} else {
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,9 +501,11 @@ export default function Estimate({}) {
//Pricing
const handlePricing = async (showPriceCd) => {
const param = {
saleStoreId: session.storeId,
sapSalesStoreCd: session.custCd,
saleStoreId: estimateContextState.sapSaleStoreId,
sapSalesStoreCd: estimateContextState.sapSalesStoreCd,
docTpCd: estimateContextState.estimateType,
secSapSalesStoreCd:
estimateContextState.secSapSalesStoreCd?.length > 0 && showPriceCd === 'QSP_PRICE' ? estimateContextState.secSapSalesStoreCd : '',
priceCd: showPriceCd,
itemIdList: estimateContextState.itemList.filter((item) => item.delFlg === '0' && item.paDispOrder === null),
}
@ -506,7 +528,6 @@ export default function Estimate({}) {
})
}
}
setIsGlobalLoading(true)
await promisePost({ url: '/api/estimate/price/item-price-list', data: param }).then((res) => {
let updateList = []
@ -696,7 +717,7 @@ export default function Estimate({}) {
/* 케이블 select 변경시 */
const onChangeDisplayCableItem = (value, itemList) => {
itemList.map((item, index) => {
if (item.dispCableFlg === '1') {
if (item.dispCableFlg === '1' && item.itemTpCd !== 'M12') {
if (value !== '') {
onChangeDisplayItem(value, item.dispOrder, index, true)
}
@ -1125,7 +1146,7 @@ export default function Estimate({}) {
dispCableFlgCnt++
}
if (item.dispCableFlg === '1') {
if (item.dispCableFlg === '1' && item.itemTpCd !== 'M12') {
setCableItem(item.itemId)
}
}
@ -1197,6 +1218,21 @@ export default function Estimate({}) {
}
}, [estimateContextState?.itemList, cableItemList])
const [agencyCustList, setAgencyCustList] = useState([])
useEffect(() => {
// 952 - 2 sapSalesStoreCd
if (estimateContextState?.sapSalesStoreCd && session?.storeLvl === '1') {
const param = {
sapSalesStoreCd: estimateContextState.sapSalesStoreCd,
}
const apiUrl = `api/estimate/agency-cust-list?${queryStringFormatter(param)}`
get({ url: apiUrl }).then((res) => {
if (isNotEmptyArray(res?.data)) {
setAgencyCustList(res?.data)
}
})
}
}, [estimateContextState?.sapSalesStoreCd])
return (
<div className="sub-content estimate">
<div className="sub-content-inner">
@ -1341,36 +1377,79 @@ export default function Estimate({}) {
{getMessage('estimate.detail.estimateType')} <span className="important">*</span>
</th>
<td colSpan={3}>
<div className="radio-wrap">
<div className="d-check-radio light mr10">
<input
type="radio"
name="estimateType"
id="YJSS"
value={'YJSS'}
checked={estimateContextState?.estimateType === 'YJSS' ? true : false}
onChange={(e) => {
//
setHandlePricingFlag(true)
setEstimateContextState({ estimateType: e.target.value })
<div className="form-flex-wrap">
<div className="radio-wrap">
{/*pkgRank is null, empty 인 경우 : 사용불가, 이전에 등록된 경우 사용가능, style로 제어*/}
<div
className="d-check-radio light mr10"
style={{
display:
(isNotEmptyArray(storePriceList) > 0 && storePriceList[0].pkgRank !== null && storePriceList[0].pkgRank !== '') ||
estimateContextState?.estimateType === 'YJSS'
? ''
: 'none',
}}
/>
<label htmlFor="YJSS">{getMessage('estimate.detail.estimateType.yjss')}</label>
</div>
<div className="d-check-radio light">
<input
type="radio"
name="estimateType"
id="YJOD"
value={'YJOD'}
checked={estimateContextState?.estimateType === 'YJOD' ? true : false}
onChange={(e) => {
setHandlePricingFlag(true)
setEstimateContextState({ estimateType: e.target.value })
}}
/>
<label htmlFor="YJOD">{getMessage('estimate.detail.estimateType.yjod')}</label>
>
<input
type="radio"
name="estimateType"
id="YJSS"
value={'YJSS'}
checked={estimateContextState?.estimateType === 'YJSS' ? true : false}
onChange={(e) => {
//
setHandlePricingFlag(true)
setEstimateContextState({ estimateType: e.target.value })
}}
/>
<label htmlFor="YJSS">{getMessage('estimate.detail.estimateType.yjss')}</label>
</div>
<div className="d-check-radio light">
<input
type="radio"
name="estimateType"
id="YJOD"
value={'YJOD'}
checked={estimateContextState?.estimateType === 'YJOD' ? true : false}
onChange={(e) => {
setHandlePricingFlag(true)
setEstimateContextState({ estimateType: e.target.value })
}}
/>
<label htmlFor="YJOD">{getMessage('estimate.detail.estimateType.yjod')}</label>
</div>
</div>
{session?.storeLvl === '1' && agencyCustList.length > 0 ? (
<div className="form-flex-select ml10">
<label htmlFor="">{getMessage('estimate.detail.agency')}</label>
<div className="select-wrap" style={{ width: '400px' }}>
<Select
id="agencyName"
instanceId="agencyName"
className="react-select-custom"
classNamePrefix="custom"
placeholder="Select"
options={agencyCustList}
onChange={(e) => {
if (isObjectNotEmpty(e)) {
setEstimateContextState({ secSapSalesStoreCd: e.sapSalesStoreCd })
} else {
setEstimateContextState({ secSapSalesStoreCd: '' })
}
}}
getOptionLabel={(x) => x.sapSalesStoreNm}
getOptionValue={(x) => x.sapSalesStoreCd}
isClearable={true}
isSearchable={true}
value={agencyCustList.filter(function (option) {
return option.sapSalesStoreCd === estimateContextState.secSapSalesStoreCd
})}
/>
</div>
</div>
) : (
''
)}
</div>
</td>
</tr>
@ -1859,8 +1938,13 @@ export default function Estimate({}) {
}
}}
menuPlacement={'auto'}
getOptionLabel={(x) => x.itemName}
getOptionLabel={(x) => x.itemName + ' (' + x.itemNo + ')'}
getOptionValue={(x) => x.itemNo}
components={{
SingleValue: ({ children, ...props }) => {
return <components.SingleValue {...props}>{props.data.itemName}</components.SingleValue>
},
}}
isClearable={false}
isDisabled={!!item?.paDispOrder}
value={displayItemList.filter(function (option) {
@ -1880,12 +1964,17 @@ export default function Estimate({}) {
placeholder="Select"
options={cableItemList}
menuPlacement={'auto'}
getOptionLabel={(x) => x.clRefChr3}
getOptionLabel={(x) => x.clRefChr3 + ' (' + x.clRefChr1 + ')'}
getOptionValue={(x) => x.clRefChr1}
components={{
SingleValue: ({ children, ...props }) => {
return <components.SingleValue {...props}>{(item.itemTpCd === 'M12')? item.itemName : props.data.clRefChr3}</components.SingleValue>
},
}}
isClearable={false}
isDisabled={true}
value={cableItemList.filter(function (option) {
return option.clRefChr1 === item.itemId
return (item.itemTpCd === 'M12')? item.itemId : option.clRefChr1 === item.itemId
})}
/>
)}
@ -1936,7 +2025,11 @@ export default function Estimate({}) {
<input
type="text"
className="input-light al-r"
value={convertNumberToPriceDecimal(item?.showSalePrice === '0' ? null : item?.salePrice?.replaceAll(',', ''))}
value={
item.openFlg === '1'
? 'OPEN'
: convertNumberToPriceDecimal(item?.showSalePrice === '0' ? null : item?.salePrice?.replaceAll(',', ''))
}
disabled={
item.openFlg === '1'
? true
@ -1964,15 +2057,17 @@ export default function Estimate({}) {
</div>
</td>
<td className="al-r">
{convertNumberToPriceDecimal(
item?.showSaleTotPrice === '0'
? null
: item?.amount === ''
? null
: item?.saleTotPrice === '0'
{item?.openFlg === '1'
? 'OPEN'
: convertNumberToPriceDecimal(
item?.showSaleTotPrice === '0'
? null
: item?.saleTotPrice?.replaceAll(',', ''),
)}
: item?.amount === ''
? null
: item?.saleTotPrice === '0'
? null
: item?.saleTotPrice?.replaceAll(',', ''),
)}
</td>
</tr>
)

View File

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

View File

@ -30,11 +30,14 @@ import { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
import { useEvent } from '@/hooks/useEvent'
import { compasDegAtom } from '@/store/orientationAtom'
import { hotkeyStore } from '@/store/hotkeyAtom'
import { usePopup } from '@/hooks/usePopup'
export default function CanvasFrame() {
const canvasRef = useRef(null)
const { canvas } = useCanvas('canvas')
const { canvasLoadInit, gridInit } = useCanvasConfigInitialize()
const { closeAll } = usePopup()
const currentMenu = useRecoilValue(currentMenuState)
const { floorPlanState } = useContext(FloorPlanContext)
const { contextMenu, handleClick } = useContextMenu()
@ -92,6 +95,8 @@ export default function CanvasFrame() {
useEffect(() => {
setIsGlobalLoading(false)
// .
closeAll()
return () => {
canvas?.clear()
@ -110,6 +115,38 @@ export default function CanvasFrame() {
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 (
<div className="canvas-frame">
<canvas ref={canvasRef} id="canvas" style={{ position: 'relative' }}></canvas>

View File

@ -31,6 +31,7 @@ export default function CanvasLayout({ children }) {
return (
<div className="canvas-layout">
<div className={`canvas-page-list ${['outline', 'surface', 'module'].includes(selectedMenu) ? 'active' : ''}`}>
<div className="canvas-id">{objectNo}</div>
<div className="canvas-plane-wrap">
{plans.map((plan, index) => (
<button

View File

@ -634,7 +634,7 @@ export default function CanvasMenu(props) {
onClick={() => setEstimatePopupOpen(true)}
>
<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 type="button" style={{ display: saveButtonStyle }} className="btn-frame gray ico-flx" onClick={handleEstimateSubmit}>
<span className="ico ico02"></span>

View File

@ -37,6 +37,8 @@ const Placement = forwardRef((props, refs) => {
}
return () => {
refs.isChidori.current = 'false'
refs.setupLocation.current = 'eaves'
setIsManualModuleSetup(false)
}
}, [])

View File

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

View File

@ -110,6 +110,7 @@ export default function PowerConditionalSelect(props) {
selected: s.pcsSerCd === data.pcsSerCd ? !s.selected : false,
}
})
setSelectedModels([])
}
setSeries(copySeries)
handleSetmodels(copySeries.filter((s) => s.selected))

View File

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

View File

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

View File

@ -38,6 +38,11 @@ export default function PanelEdit(props) {
const isSetupModules = canvas.getObjects().filter((obj) => obj.name === 'module') // selectedObj
isSetupModules.forEach((obj) => obj.set({ lockMovementX: false, lockMovementY: false }))
}
//
return () => {
canvas?.discardActiveObject() //
}
}, [])
//
@ -87,7 +92,7 @@ export default function PanelEdit(props) {
moduleMultiCopy('row', length, direction)
break
}
closePopup(id)
// closePopup(id)
}
return (

View File

@ -19,6 +19,7 @@ import { getChonByDegree, getDegreeByChon } from '@/util/canvas-util'
import { usePolygon } from '@/hooks/usePolygon'
import { canvasState } from '@/store/canvasAtom'
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 canvas = useRecoilValue(canvasState)
const roofDisplay = useRecoilValue(roofDisplaySelector)
const { saveCanvas } = usePlan()
const roofRef = {
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 = {
...currentRoof,
planNo: basicSetting.planNo,
@ -254,6 +256,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
/* 저장 후 화면 닫기 */
closePopup(id)
await saveCanvas(false)
}
return (
@ -271,7 +274,11 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
<tbody>
<tr>
<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>
<th>

View File

@ -203,7 +203,7 @@ export default function Stuff() {
if (event.column.colId === 'objectNo') {
return
} else {
//T R
//T S
if (event.data.objectNo) {
setIsGlobalLoading(true)
if (event.data.tempFlg === '0') {

View File

@ -54,7 +54,7 @@ export default function StuffDetail() {
const { get, promiseGet, del, promisePost, promisePut } = useAxios(globalLocaleState)
//form
const formInitValue = {
// T...() R...()
// T...() S...()
planReqNo: '', //No
receiveUser: session?.userNm, //
objectStatusId: '0', //(:0 : 1)
@ -1033,8 +1033,7 @@ export default function StuffDetail() {
const _saleStoreId = watch('saleStoreId')
// 2
const _otherSaleStoreId = watch('otherSaleStoreId')
// zipNo: '', //
const _zipNo = watch('zipNo')
// zipNo: '', // #947
// prefId: '', //
const _prefId = watch('prefId')
// address: '', //
@ -1071,10 +1070,6 @@ export default function StuffDetail() {
}
}
if (!formData.zipNo) {
errors.zipNo = true
}
if (!formData.prefId) {
errors.prefId = true
}
@ -1115,10 +1110,6 @@ export default function StuffDetail() {
}
}
if (!formData.zipNo) {
errors.zipNo = true
}
if (!formData.prefId || formData.prefId === '0') {
errors.prefId = true
}
@ -1144,7 +1135,6 @@ export default function StuffDetail() {
_objectName,
_saleStoreId,
_otherSaleStoreId,
_zipNo,
_prefId,
_address,
_areaId,
@ -1189,6 +1179,14 @@ export default function StuffDetail() {
}
}, [prefValue])
// / disabled
const onChangePrefCode = (e) => {
setPrefValue(e.prefId)
form.setValue('prefId', e.prefId)
form.setValue('prefName', e.prefName)
}
//
const handleAreaIdOnChange = (e) => {
form.setValue('areaId', e.areaId)
@ -1243,12 +1241,6 @@ export default function StuffDetail() {
errors = fieldNm
}
//
if (!formData.zipNo) {
fieldNm = getMessage('stuff.detail.zipNo')
errors = fieldNm
}
//1
if (!formData.saleStoreId) {
fieldNm = getMessage('stuff.detail.saleStoreId')
@ -1558,7 +1550,7 @@ export default function StuffDetail() {
type: 'alert',
icon: 'error',
})
console.log('error::::::', error)
console.error('error::::::', error)
})
}
}
@ -2004,9 +1996,7 @@ export default function StuffDetail() {
</td>
</tr>
<tr>
<th>
{getMessage('stuff.detail.zipNo')} <span className="important">*</span>
</th>
<th>{getMessage('stuff.detail.zipNo')}</th>
<td>
<div className="flx-box">
<div className="input-wrap mr5" style={{ width: '200px' }}>
@ -2038,10 +2028,10 @@ export default function StuffDetail() {
getOptionLabel={(x) => x.prefName}
getOptionValue={(x) => x.prefId}
isSearchable={false}
onChange={onChangePrefCode}
value={prefCodeList.filter(function (option) {
return option.prefId === prefValue
})}
isDisabled={true}
/>
)}
</div>
@ -2571,9 +2561,7 @@ export default function StuffDetail() {
</td>
</tr>
<tr>
<th>
{getMessage('stuff.detail.zipNo')} <span className="important">*</span>
</th>
<th>{getMessage('stuff.detail.zipNo')}</th>
<td>
<div className="flx-box">
<div className="input-wrap mr5" style={{ width: '200px' }}>
@ -2606,10 +2594,10 @@ export default function StuffDetail() {
getOptionLabel={(x) => x.prefName}
getOptionValue={(x) => x.prefId}
isSearchable={false}
onChange={onChangePrefCode}
value={prefCodeList.filter(function (option) {
return option.prefId === prefValue
})}
isDisabled={true}
/>
)}
</div>

View File

@ -7,8 +7,6 @@ import { POLYGON_TYPE } from '@/common/common'
export const useCanvasMenu = () => {
const [selectedMenu, setSelectedMenu] = useRecoilState(selectedMenuState)
const canvas = useRecoilValue(canvasState)
const { drawDirectionArrow } = usePolygon()
return {
selectedMenu,

View File

@ -130,13 +130,14 @@ export const useEstimateController = (planNo, flag) => {
addFlg: true,
paDispOrder: null,
dispCableFlg: '0',
itemTpCd: '',
},
],
})
}
useEffect(() => {
setEstimateData({ ...estimateContextState, userId: session.userId, sapSalesStoreCd: session.custCd })
setEstimateData({ ...estimateContextState, userId: session.userId })
}, [estimateContextState])
// 첨부파일 다운로드
@ -321,6 +322,7 @@ export const useEstimateController = (planNo, flag) => {
}
})
if (delCnt === estimateData.itemList.length) {
itemFlg = false
setIsGlobalLoading(false)
return swalFire({ text: getMessage('estimate.detail.save.requiredItem'), type: 'alert', icon: 'warning' })
}
@ -452,8 +454,6 @@ export const useEstimateController = (planNo, flag) => {
}
const params = {
saleStoreId: session.storeId,
sapSalesStoreCd: session.custCd,
objectNo: estimateData.objectNo,
planNo: sendPlanNo,
copySaleStoreId: otherSaleStoreId ? otherSaleStoreId : saleStoreId,
@ -462,24 +462,30 @@ export const useEstimateController = (planNo, flag) => {
}
setIsGlobalLoading(true)
await promisePost({ url: '/api/estimate/save-estimate-copy', data: params }).then((res) => {
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 {
await promisePost({ url: '/api/estimate/save-estimate-copy', data: params })
.then((res) => {
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) => {

View File

@ -54,7 +54,7 @@ export function useModule() {
})
return
}
canvas.discardActiveObject() //선택해제
// canvas.discardActiveObject() //선택해제
const isSetupModules = getOtherModules(selectedObj)
const selectedModules = canvas.getObjects().filter((obj) => selectedIds.includes(obj.id) && obj.name === 'module') //선택했던 객체들만 가져옴
@ -991,14 +991,14 @@ export function useModule() {
const getRowModules = (target) => {
return canvas
.getObjects()
.filter((obj) => target.surfaceId === obj.surfaceId && obj.name === POLYGON_TYPE.MODULE && obj.top === target.top)
.filter((obj) => target.surfaceId === obj.surfaceId && obj.name === POLYGON_TYPE.MODULE && Math.abs(obj.top - target.top) < 1)
.sort((a, b) => a.left - b.left)
}
const getColumnModules = (target) => {
return canvas
.getObjects()
.filter((obj) => target.surfaceId === obj.surfaceId && obj.name === POLYGON_TYPE.MODULE && obj.left === target.left)
.filter((obj) => target.surfaceId === obj.surfaceId && obj.name === POLYGON_TYPE.MODULE && Math.abs(obj.left - target.left) < 1)
.sort((a, b) => a.top - b.top)
}

View File

@ -2,10 +2,10 @@ import { useState } from 'react'
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
import { canvasSettingState, canvasState, checkedModuleState, currentObjectState, isManualModuleSetupState } from '@/store/canvasAtom'
import { rectToPolygon, polygonToTurfPolygon, calculateVisibleModuleHeight, getDegreeByChon, toFixedWithoutRounding } from '@/util/canvas-util'
import { basicSettingState, roofDisplaySelector } from '@/store/settingAtom'
import { roofDisplaySelector } from '@/store/settingAtom'
import offsetPolygon, { calculateAngle, createLinesFromPolygon } from '@/util/qpolygon-utils'
import { QPolygon } from '@/components/fabric/QPolygon'
import { moduleSetupSurfaceState, moduleIsSetupState } from '@/store/canvasAtom'
import { moduleSetupSurfaceState } from '@/store/canvasAtom'
import { useEvent } from '@/hooks/useEvent'
import { POLYGON_TYPE, BATCH_TYPE, LINE_TYPE } from '@/common/common'
import * as turf from '@turf/turf'
@ -15,7 +15,6 @@ import { QLine } from '@/components/fabric/QLine'
import { useRoofFn } from '@/hooks/common/useRoofFn'
import { useEffect } from 'react'
import { useMessage } from '@/hooks/useMessage'
import { moduleStatisticsState } from '@/store/circuitTrestleAtom'
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
import { useMasterController } from '@/hooks/common/useMasterController'
import { v4 as uuidv4 } from 'uuid'
@ -30,12 +29,10 @@ export function useModuleBasicSetting(tabNum) {
const [moduleSetupSurface, setModuleSetupSurface] = useRecoilState(moduleSetupSurfaceState)
const { addCanvasMouseEventListener, initEvent, removeMouseEvent, addTargetMouseEventListener } = useEvent()
const { swalFire } = useSwal()
const compasDeg = useRecoilValue(compasDegAtom)
const { setSurfaceShapePattern } = useRoofFn()
const checkedModule = useRecoilValue(checkedModuleState)
const [isManualModuleSetup, setIsManualModuleSetup] = useRecoilState(isManualModuleSetupState)
const setModuleStatistics = useSetRecoilState(moduleStatisticsState)
const canvasSetting = useRecoilValue(canvasSettingState)
@ -46,19 +43,11 @@ export function useModuleBasicSetting(tabNum) {
const { getTrestleDetailList } = useMasterController()
const [saleStoreNorthFlg, setSaleStoreNorthFlg] = useState(false)
const [currentObject, setCurrentObject] = useRecoilState(currentObjectState)
const setCurrentObject = useSetRecoilState(currentObjectState)
const { setModuleStatisticsData } = useCircuitTrestle()
const { createRoofPolygon, createMarginPolygon, createPaddingPolygon } = useMode()
useEffect(() => {
// console.log('basicSetting', basicSetting)
if (canvas) {
//드래그 여부
// canvas.selection = true
// canvas.selectionFullyContained = true
}
return () => {
//수동 설치시 초기화
removeMouseEvent('mouse:up')
@ -67,8 +56,6 @@ export function useModuleBasicSetting(tabNum) {
}
}, [])
// const { addTargetMouseEventListener, addCanvasMouseEventListener, initEvent } = useContext(EventContext)
//모듈 선택에서 선택된 값들 넘어옴
const makeModuleInitArea = () => {
if (isObjectNotEmpty(moduleSelectionData) && tabNum === 3) {
@ -144,10 +131,7 @@ export function useModuleBasicSetting(tabNum) {
//roof에 상세 데이터 추가
roof.set({ trestleDetail: detail.data })
roof.lines.forEach((line) => {
line.attributes = {
...line.attributes,
offset: getOffset(detail.data, line.attributes.type),
}
line.attributes = { ...line.attributes, offset: getOffset(detail.data, line.attributes.type) }
})
//배치면 설치 영역
makeModuleInstArea(roof, detail.data)
@ -353,12 +337,7 @@ export function useModuleBasicSetting(tabNum) {
}
//기본 선택이랑 스트로크 굵기가 같으면 선택 안됨으로 봄
setupSurface.set({
...setupSurface,
strokeWidth: 3,
strokeDashArray: [0],
fill: 'rgba(255,255,255,0.1)',
})
setupSurface.set({ ...setupSurface, strokeWidth: 3, strokeDashArray: [0], fill: 'rgba(255,255,255,0.1)' })
canvas.discardActiveObject() // 객체의 활성 상태 해제
//중복으로 들어가는걸 방지하기 위한 코드
@ -368,12 +347,7 @@ export function useModuleBasicSetting(tabNum) {
setCurrentObject({ name: 'moduleSetupSurface', arrayData: [...selectedModuleInstSurfaceArray] })
} else {
//선택후 재선택하면 선택안됨으로 변경
setupSurface.set({
...setupSurface,
fill: 'rgba(255,255,255,0.1)',
strokeDashArray: [10, 4],
strokeWidth: 1,
})
setupSurface.set({ ...setupSurface, fill: 'rgba(255,255,255,0.1)', strokeDashArray: [10, 4], strokeWidth: 1 })
canvas.discardActiveObject() // 객체의 활성 상태 해제
//폴리곤에 커스텀 인덱스를 가지고 해당 배열 인덱스를 찾아 삭제함
@ -424,8 +398,6 @@ export function useModuleBasicSetting(tabNum) {
* 확인 셀을 이동시킴
*/
const manualModuleSetup = (placementRef) => {
console.log('isManualModuleSetup', isManualModuleSetup)
const moduleSetupSurfaces = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) //모듈설치면를 가져옴
if (isManualModuleSetup) {
@ -444,10 +416,7 @@ export function useModuleBasicSetting(tabNum) {
const batchObjects = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.OBJECT_SURFACE) //도머s 객체
//수동모드 모듈 설치면 선택 잠금
moduleSetupSurfaces.forEach((obj) => {
obj.set({
selectable: false,
evented: false,
})
obj.set({ selectable: false, evented: false })
})
//모듈 기본 옵션
@ -549,6 +518,7 @@ export function useModuleBasicSetting(tabNum) {
* 스냅기능
*/
let snapDistance = flowDirection === 'south' || flowDirection === 'north' ? 70 : 40
let sideSnapDistance = 15
let trestleSnapDistance = 15
let intvHor =
@ -588,51 +558,102 @@ export function useModuleBasicSetting(tabNum) {
const holdCellCenterX = holdCellLeft + toFixedWithoutRounding(cell.width / 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) {
@ -641,11 +662,6 @@ export function useModuleBasicSetting(tabNum) {
// //위쪽 -> 가운데
// if (Math.abs(smallTop - holdCellCenterY) < cellSnapDistance) {
// tempModule.top = holdCellCenterY
// }
// //아랫쪽 -> 가운데
// if (Math.abs(smallBottom - holdCellCenterY) < cellSnapDistance) {
// tempModule.top = holdCellCenterY - height
// }
})
}
@ -673,22 +689,19 @@ export function useModuleBasicSetting(tabNum) {
// if (Math.abs(smallLeft - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < snapDistance) {
// tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2
// }
// 모듈이 가운데가 세로중앙선에 붙게 스냅
if (Math.abs(smallCenterX - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < trestleSnapDistance) {
tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 - tempModule.width / 2
}
// if (Math.abs(smallCenterX - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < trestleSnapDistance) {
// tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 - tempModule.width / 2
// }
// 모듈오른쪽이 세로중앙선에 붙게 스냅
// if (Math.abs(smallRight - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < trestleSnapDistance) {
// tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 - tempModule.width * tempModule.scaleX
// }
} else {
// 모듈이 가로중앙선에 스냅
if (Math.abs(smallTop + tempModule.height / 2 - bigCenterY) < trestleSnapDistance) {
tempModule.top = bigCenterY - tempModule.height / 2
}
// if (Math.abs(smallTop + tempModule.height / 2 - bigCenterY) < trestleSnapDistance) {
// tempModule.top = bigCenterY - tempModule.height / 2
// }
// if (Math.abs(smallTop - (trestleTop + moduleSetupSurfaces[i].height / 2)) < trestleSnapDistance) {
// tempModule.top = trestleTop + moduleSetupSurfaces[i].height / 2
// }
@ -792,10 +805,7 @@ export function useModuleBasicSetting(tabNum) {
if (moduleSetupSurfaces) {
//수동모드 해제시 모듈 설치면 선택 잠금
moduleSetupSurfaces.forEach((obj) => {
obj.set({
selectable: true,
evented: true,
})
obj.set({ selectable: true, evented: true })
})
}
@ -983,7 +993,7 @@ export function useModuleBasicSetting(tabNum) {
//근데 양변이 곡선이면 중앙에 맞추기 위해 아래와 위의 길이를 재서 모듈의 길이를 나눠서 들어갈수 있는 갯수가 동일하면 가운데로 정렬 시킨다
if (flowLines.left.type === 'curve' && flowLines.right.type === 'curve') {
startPointX = isChidori ? flowLines.left.x1 + 1 : flowLines.left.x1 + (calcAreaWidth - totalModuleWidthCount * width) / 2
startPointX = flowLines.left.x1 + (calcAreaWidth - totalModuleWidthCount * width) / 2
}
let heightMargin = 0
@ -1012,7 +1022,7 @@ export function useModuleBasicSetting(tabNum) {
let moduleX = startPointX + width * j + 1 //5정도 마진을 준다
widthMargin = j === 0 ? 0 : intvHor * j // 가로 마진값
chidoriLength = 0 //치도리가 아니여도 기본값을 5정도 준다
if (isChidori && !isMaxSetup) {
if (isChidori) {
chidoriLength = installedModuleHeightCount % 2 == 0 ? 0 : width / 2 - intvHor
}
@ -1684,11 +1694,7 @@ export function useModuleBasicSetting(tabNum) {
const pointY2 = top
//디버깅
const finalLine = new QLine([pointX1, pointY1, pointX2, pointY2], {
stroke: 'red',
strokeWidth: 1,
selectable: true,
})
const finalLine = new QLine([pointX1, pointY1, pointX2, pointY2], { stroke: 'red', strokeWidth: 1, selectable: true })
// console.log(`index ${index} : finalLine`, pointX1, pointY1, pointX2, pointY2)
@ -1810,11 +1816,7 @@ export function useModuleBasicSetting(tabNum) {
const pointY2 = coords[2].y + ((coords[2].x - top) / (coords[2].x - coords[1].x)) * (coords[1].y - coords[2].y)
//디버깅용
const finalLine = new QLine([pointX1, pointY1, pointX2, pointY2], {
stroke: 'red',
strokeWidth: 1,
selectable: true,
})
const finalLine = new QLine([pointX1, pointY1, pointX2, pointY2], { stroke: 'red', strokeWidth: 1, selectable: true })
// canvas?.add(finalLine)
// canvas?.renderAll()
@ -1896,12 +1898,7 @@ export function useModuleBasicSetting(tabNum) {
{ x1: 0, y1: 0, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스
)
const obj = {
left: leftFlow,
right: rightFlow,
top: topFlow,
bottom: bottomFlow,
}
const obj = { left: leftFlow, right: rightFlow, top: topFlow, bottom: bottomFlow }
return obj
}
@ -2196,10 +2193,7 @@ export function useModuleBasicSetting(tabNum) {
const rectPoints = [
{ x: tempModule.left, y: tempModule.top },
{ x: tempModule.left + tempModule.width * tempModule.scaleX, y: tempModule.top },
{
x: tempModule.left + tempModule.width * tempModule.scaleX,
y: tempModule.top + tempModule.height * tempModule.scaleY,
},
{ x: tempModule.left + tempModule.width * tempModule.scaleX, y: tempModule.top + tempModule.height * tempModule.scaleY },
{ x: tempModule.left, y: tempModule.top + tempModule.height * tempModule.scaleY },
]
@ -2254,10 +2248,7 @@ export function useModuleBasicSetting(tabNum) {
if (moduleSetupSurfaces) {
//수동모드 해제시 모듈 설치면 선택 잠금
moduleSetupSurfaces.forEach((obj) => {
obj.set({
selectable: true,
evented: true,
})
obj.set({ selectable: true, evented: true })
})
}

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 { QcastContext } from '@/app/QcastProvider'
import { useCircuitTrestle } from '@/hooks/useCirCuitTrestle'
import { useMessage } from '@/hooks/useMessage'
// 모듈간 같은 행, 열의 마진이 10 이하인 경우는 같은 행, 열로 간주
const MODULE_MARGIN = 10
@ -26,6 +27,7 @@ export const useTrestle = () => {
const { getSelectedPcsItemList } = useCircuitTrestle()
const { resetCircuits } = useCircuitTrestle()
const { getMessage } = useMessage()
const apply = () => {
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
if (!construction) {
swalFire({ text: 'construction 존재안함', icon: 'error' })
return
}
@ -131,9 +132,9 @@ export const useTrestle = () => {
surface.isChidory = isChidory
if (plvrYn === 'N' && isChidory) {
swalFire({ text: '치조불가공법입니다.', icon: 'error' })
swalFire({ text: getMessage('chidory.can.not.install'), icon: 'error' })
clear()
throw new Error('치조불가공법입니다.')
throw new Error(getMessage('chidory.can.not.install'))
}
surface.set({ isChidory: isChidory })
@ -999,9 +1000,7 @@ export const useTrestle = () => {
if (!rackInfos) {
const maxRows = surface.trestleDetail.moduleMaxRows
const maxCols = surface.trestleDetail.moduleMaxCols
const msg = `選択した家で設置可能
モジュールの最大段数は${maxRows}最大列数は${maxCols}です
上限より上部に取り付けたモジュールを削除してください`
const msg = `段数の上限は${maxRows}段です。 上限より上の段には設置できません`
swalFire({ title: msg, type: 'alert' })
throw new Error('rackInfos is null')
}

View File

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

View File

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

View File

@ -32,6 +32,7 @@ import { outlineDisplaySelector } from '@/store/settingAtom'
import { usePopup } from '@/hooks/usePopup'
import PropertiesSetting from '@/components/floor-plan/modal/outerlinesetting/PropertiesSetting'
import Big from 'big.js'
import RoofShapeSetting from '@/components/floor-plan/modal/roofShape/RoofShapeSetting'
//외벽선 그리기
export function useOuterLineWall(id, propertiesId) {
@ -256,7 +257,7 @@ export function useOuterLineWall(id, propertiesId) {
canvas?.renderAll()
setOuterLineFix(true)
closePopup(id)
addPopup(propertiesId, 1, <PropertiesSetting id={propertiesId} pos={{ x: 50, y: 230 }} />)
addPopup(propertiesId, 1, <RoofShapeSetting id={propertiesId} pos={{ x: 50, y: 230 }} />)
}
if (points.length < 3) {

View File

@ -1,6 +1,6 @@
import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'
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 { useSwal } from '@/hooks/useSwal'
import { usePolygon } from '@/hooks/usePolygon'
@ -26,6 +26,8 @@ import { getChonByDegree, getDegreeByChon } from '@/util/canvas-util'
import { moduleSelectionDataState } from '@/store/selectedModuleOptions'
import { useCanvasPopupStatusController } from '@/hooks/common/useCanvasPopupStatusController'
import { outerLinePointsState } from '@/store/outerLineAtom'
import { QcastContext } from '@/app/QcastProvider'
import { usePlan } from '@/hooks/usePlan'
export function useRoofAllocationSetting(id) {
const canvas = useRecoilValue(canvasState)
@ -49,8 +51,9 @@ export function useRoofAllocationSetting(id) {
const { get, post } = useAxios(globalLocaleState)
const { getMessage } = useMessage()
const { swalFire } = useSwal()
const { setIsGlobalLoading } = useContext(QcastContext)
const { setSurfaceShapePattern } = useRoofFn()
const { saveCanvas } = usePlan()
const [moduleSelectionData, setModuleSelectionData] = useRecoilState(moduleSelectionDataState)
const resetPoints = useResetRecoilState(outerLinePointsState)
@ -68,30 +71,19 @@ export function useRoofAllocationSetting(id) {
roof.innerLines.forEach((line) => {
/** 실측값이 없는 경우 라인 두께 4로 설정 */
if (!line.attributes.actualSize || line.attributes?.actualSize === 0) {
line.set({
strokeWidth: 4,
stroke: 'black',
selectable: true,
})
line.set({ strokeWidth: 4, stroke: 'black', selectable: true })
}
/** 현재 선택된 라인인 경우 라인 두께 2로 설정 */
if (editingLines.includes(line)) {
line.set({
strokeWidth: 2,
stroke: 'black',
selectable: true,
})
line.set({ strokeWidth: 2, stroke: 'black', selectable: true })
}
})
})
/** 현재 선택된 객체가 보조라인, 피라미드, 힙인 경우 두께 4로 설정 */
if (currentObject && currentObject.name && ['auxiliaryLine', 'ridge', 'hip'].includes(currentObject.name)) {
currentObject.set({
strokeWidth: 4,
stroke: '#EA10AC',
})
currentObject.set({ strokeWidth: 4, stroke: '#EA10AC' })
}
}, [currentObject])
@ -99,7 +91,7 @@ export function useRoofAllocationSetting(id) {
/** 현재 선택된 객체가 보조라인, 피라미드, 힙인 경우 두께 4로 설정 */
const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
if (roofBases.length === 0) {
swalFire({ text: '할당할 지붕이 없습니다.' })
swalFire({ text: getMessage('roofAllocation.not.found'), icon: 'warning' })
closePopup(id)
}
@ -112,9 +104,7 @@ export function useRoofAllocationSetting(id) {
*/
const fetchBasicSettings = async (planNo) => {
try {
await get({
url: `/api/canvas-management/canvas-basic-settings/by-object/${correntObjectNo}/${planNo}`,
}).then((res) => {
await get({ url: `/api/canvas-management/canvas-basic-settings/by-object/${correntObjectNo}/${planNo}` }).then((res) => {
let roofsArray = {}
if (res.length > 0) {
@ -184,11 +174,7 @@ export function useRoofAllocationSetting(id) {
selectedRoofMaterial: selectRoofs.find((roof) => roof.selected),
})
setBasicInfo({
planNo: '' + res[0].planNo,
roofSizeSet: '' + res[0].roofSizeSet,
roofAngleSet: '' + res[0].roofAngleSet,
})
setBasicInfo({ planNo: '' + res[0].planNo, roofSizeSet: '' + res[0].roofSizeSet, roofAngleSet: '' + res[0].roofAngleSet })
})
} catch (error) {
console.error('Data fetching error:', error)
@ -200,6 +186,7 @@ export function useRoofAllocationSetting(id) {
*/
const basicSettingSave = async () => {
try {
setIsGlobalLoading(true)
const patternData = {
objectNo: correntObjectNo,
planNo: Number(basicSetting.planNo),
@ -222,6 +209,8 @@ export function useRoofAllocationSetting(id) {
await post({ url: `/api/canvas-management/roof-allocation-settings`, data: patternData }).then((res) => {
swalFire({ text: getMessage(res.returnMessage) })
setIsGlobalLoading(false)
saveCanvas(false)
})
//Recoil 설정
@ -242,22 +231,34 @@ export function useRoofAllocationSetting(id) {
swalFire({ type: 'alert', icon: 'error', text: getMessage('roof.exceed.count') })
return
}
setCurrentRoofList([
...currentRoofList,
{
...currentRoofMaterial,
selected: false,
id: currentRoofMaterial.roofMatlCd,
name: currentRoofMaterial.roofMatlNm,
index: currentRoofList.length,
},
])
const originCurrentRoofList = currentRoofList.map((roof) => {
return { ...roof, selected: false }
})
originCurrentRoofList.push({
...currentRoofMaterial,
selected: true,
id: currentRoofMaterial.roofMatlCd,
name: currentRoofMaterial.roofMatlNm,
index: currentRoofList.length,
})
setCurrentRoofList(originCurrentRoofList)
}
/**
* 지붕재 삭제
*/
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 newRoofList = JSON.parse(JSON.stringify(currentRoofList)).filter((_, index) => index !== idx)
if (isSelected) {
@ -270,8 +271,6 @@ export function useRoofAllocationSetting(id) {
* 선택한 지붕재로 할당
*/
const handleSave = () => {
basicSettingSave()
/**
* 모두 actualSize 있으면 바로 적용 없으면 actualSize 설정
*/
@ -280,6 +279,7 @@ export function useRoofAllocationSetting(id) {
} else {
apply()
resetPoints()
basicSettingSave()
}
}
@ -287,23 +287,45 @@ export function useRoofAllocationSetting(id) {
* 지붕재 오른쪽 마우스 클릭 단일로 지붕재 변경 필요한 경우
*/
const handleSaveContext = () => {
basicSettingSave()
const newRoofList = currentRoofList.map((roof, idx) => {
if (roof.index !== idx) {
// 기존 저장된 지붕재의 index 수정
const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF && obj.roofMaterial?.index === roof.index)
roofs.forEach((roof) => {
setSurfaceShapePattern(roof, roofDisplay.column, false, { ...roof, index: idx }, true)
})
}
return { ...roof, index: idx, raft: roof.raft ? roof.raft : roof.raftBaseCd }
})
setBasicSetting((prev) => {
return {
...prev,
selectedRoofMaterial: newRoofList.find((roof) => roof.selected),
}
return { ...prev, selectedRoofMaterial: newRoofList.find((roof) => roof.selected) }
})
setRoofList(newRoofList)
setRoofMaterials(newRoofList)
const selectedRoofMaterial = newRoofList.find((roof) => roof.selected)
setSurfaceShapePattern(currentObject, roofDisplay.column, false, selectedRoofMaterial, true)
drawDirectionArrow(currentObject)
modifyModuleSelectionData()
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)
})
})
}
/**
@ -313,11 +335,7 @@ export function useRoofAllocationSetting(id) {
if (!checkInnerLines()) {
apply()
} else {
swalFire({
type: 'alert',
icon: 'error',
text: getMessage('실제치수를 입력해 주세요.'),
})
swalFire({ type: 'alert', icon: 'error', text: getMessage('실제치수를 입력해 주세요.') })
}
}
@ -332,11 +350,7 @@ export function useRoofAllocationSetting(id) {
if (roof.separatePolygon.length === 0) {
roof.innerLines.forEach((line) => {
if (!line.attributes.actualSize || line.attributes?.actualSize === 0) {
line.set({
strokeWidth: 4,
stroke: 'black',
selectable: true,
})
line.set({ strokeWidth: 4, stroke: 'black', selectable: true })
result = true
}
})
@ -361,6 +375,7 @@ export function useRoofAllocationSetting(id) {
splitPolygonWithLines(roofBase)
}
} catch (e) {
console.log(e)
return
}
@ -383,10 +398,7 @@ export function useRoofAllocationSetting(id) {
})
setBasicSetting((prev) => {
return {
...prev,
selectedRoofMaterial: newRoofList.find((roof) => roof.selected),
}
return { ...prev, selectedRoofMaterial: newRoofList.find((roof) => roof.selected) }
})
setRoofList(newRoofList)
@ -394,9 +406,7 @@ export function useRoofAllocationSetting(id) {
roofs.forEach((roof) => {
if (roof.isFixed) return
roof.set({
isFixed: true,
})
roof.set({ isFixed: true })
/** 모양 패턴 설정 */
setSurfaceShapePattern(
@ -408,6 +418,8 @@ export function useRoofAllocationSetting(id) {
drawDirectionArrow(roof)
})
setRoofMaterials(newRoofList)
/** 외곽선 삭제 */
const removeTargets = canvas.getObjects().filter((obj) => obj.name === 'outerLinePoint' || obj.name === 'outerLine')
removeTargets.forEach((obj) => {
@ -431,10 +443,7 @@ export function useRoofAllocationSetting(id) {
if (id === line.id) {
setEditingLines([...editingLines.filter((editLine) => editLine.id !== line.id), line])
line.attributes.actualSize = size
line.set({
strokeWidth: 2,
stroke: 'black',
})
line.set({ strokeWidth: 2, stroke: 'black' })
}
})
})

View File

@ -28,7 +28,7 @@ export function useRoofShapePassivitySetting(id) {
const { addCanvasMouseEventListener, initEvent } = useEvent()
// const { addCanvasMouseEventListener, initEvent } = useContext(EventContext)
const { drawRoofPolygon } = useMode()
const { addPolygonByLines } = usePolygon()
const { addPolygonByLines, addLengthText } = usePolygon()
const currentObject = useRecoilValue(currentObjectState)
const offsetRef = useRef(null)
const pitchRef = useRef(null)
@ -51,7 +51,7 @@ export function useRoofShapePassivitySetting(id) {
useEffect(() => {
const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine')
if (!outerLineFix || outerLines.length === 0) {
swalFire({ text: '외벽선이 없습니다.' })
swalFire({ text: getMessage('wall.line.not.found') })
closePopup(id)
return
}
@ -248,6 +248,7 @@ export function useRoofShapePassivitySetting(id) {
// 완료 한 경우에는 지붕까지 그려줌
addPitchTextsByOuterLines()
const roof = drawRoofPolygon(wall)
addLengthText(roof)
}
canvas.renderAll()

View File

@ -191,7 +191,7 @@ export function useRoofShapeSetting(id) {
let direction
if (outerLines.length < 2) {
swalFire({ text: '외벽선이 없습니다.', icon: 'error' })
swalFire({ text: getMessage('wall.line.not.found') })
return
}
@ -716,6 +716,7 @@ export function useRoofShapeSetting(id) {
type: LINE_TYPE.WALLLINE.SHED,
pitch: shedPitchRef.current,
width: shedWidth / 10,
offset: shedWidth / 10,
}
selectedLine.attributes = { ...attributes, isFixed: true }
addPitchText(currentObject)

View File

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

View File

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

View File

@ -3,7 +3,7 @@
import { useEffect } from 'react'
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
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 { degreesToRadians } from '@turf/turf'
import { QPolygon } from '@/components/fabric/QPolygon'
@ -111,7 +111,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
lockScalingX: true, // X 축 크기 조정 잠금
lockScalingY: true, // Y 축 크기 조정 잠금
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: rotate,
originX: 'center',
@ -120,6 +120,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
}
obj = new QPolygon(points, options)
let imageRotate = 0
if (xInversion && !yInversion) {
if (rotate % 180 === 0 || rotate < 0) {
@ -148,7 +149,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
} else {
imageRotate = (rotate + 360) % 360
}
obj.set({ angle: imageRotate })
obj.set({ angle: imageRotate, flipX: xInversion !== yInversion })
obj.setCoords() //좌표 변경 적용
canvas?.add(obj)
@ -158,6 +159,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
addCanvasMouseEventListener('mouse:down', (e) => {
isDrawing = false
const { xInversion, yInversion } = surfaceRefs
canvas?.remove(obj)
//각도 추가
@ -178,6 +180,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
}
//회전, flip등이 먹은 기준으로 새로생성
// const batchSurface = addPolygon(reorderedPoints, {
const batchSurface = addPolygon(obj.getCurrentPoints(), {
fill: 'transparent',
stroke: 'red',
@ -196,18 +199,25 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
pitch: globalPitch,
surfaceId: surfaceId,
direction: direction,
isXInversion: xInversion,
isYInversion: yInversion,
})
canvas.setActiveObject(batchSurface)
setSurfaceShapePattern(batchSurface, roofDisplay.column)
drawDirectionArrow(batchSurface)
// if (setIsHidden) setIsHidden(false)
// closePopup(id)
initEvent()
if (+canvasSetting?.roofSizeSet === 3) return
const popupId = uuidv4()
addPopup(popupId, 2, <PlacementSurfaceLineProperty roof={batchSurface} id={popupId} setIsHidden={setIsHidden} />)
// if (+canvasSetting?.roofSizeSet === 3) return
// const popupId = uuidv4()
// 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 {
if (setIsHidden) setIsHidden(false)
@ -488,18 +498,18 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
}
case 10: {
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 + length2, y: pointer.y + length4 / 2 - length5 },
{
x: pointer.x + length1 / 2 - length1 + length2,
y: pointer.y + length4 / 2 - length5 - (length4 - length5),
},
{ x: pointer.x + length1 / 2 - length1, y: pointer.y + length4 / 2 },
{ x: pointer.x + length1 / 2, y: pointer.y + length4 / 2 },
{
x: pointer.x + length1 / 2 - length1 + length2 + length3,
y: pointer.y + length4 / 2 - length5 - (length4 - length5),
},
{
x: pointer.x + length1 / 2 - length1 + length2,
y: pointer.y + length4 / 2 - length5 - (length4 - length5),
},
{ x: pointer.x + length1 / 2 - length1 + length2, y: pointer.y + length4 / 2 - length5 },
]
break
}
@ -613,27 +623,27 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
}
case 14: {
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 + length1, y: pointer.y + length4 / 2 - length4 },
{ x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length4 + length4 },
{ x: pointer.x - length1 / 2 + length1 - length3, y: pointer.y + length4 / 2 - length4 + length4 },
{ x: pointer.x - length1 / 2, y: pointer.y + length4 / 2 },
{ x: pointer.x - length1 / 2 + length2, y: pointer.y + length4 / 2 },
{
x: pointer.x - length1 / 2 + length2 + (length1 - length2 - length3) / 2,
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
}
case 15: {
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, y: pointer.y + length2 - length2 / 2 - length3 - (length2 - length3) },
{ x: pointer.x + length1 / 2, y: pointer.y + length2 - length2 / 2 - length3 },
{ x: pointer.x - length1 / 2, y: pointer.y + length2 - length2 / 2 },
{ 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
}
@ -641,28 +651,28 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
case 16: {
points = [
{
x: pointer.x - length1 / 2,
y: pointer.y + length3 / 2,
x: pointer.x - length1 / 2 + (length1 - length2) / 2,
y: pointer.y + length3 / 2 - (length3 - length4) - length4,
},
{
x: pointer.x - length1 / 2 + (length1 - length2) / 2,
y: pointer.y + length3 / 2 - (length3 - length4),
},
{
x: pointer.x - length1 / 2 + (length1 - length2) / 2,
y: pointer.y + length3 / 2 - (length3 - length4) - length4,
x: pointer.x - length1 / 2,
y: pointer.y + length3 / 2,
},
{
x: pointer.x - length1 / 2 + (length1 - length2) / 2 + length2,
y: pointer.y + length3 / 2 - (length3 - length4) - length4,
x: pointer.x - length1 / 2 + length1,
y: pointer.y + length3 / 2,
},
{
x: pointer.x - length1 / 2 + (length1 - length2) / 2 + length2,
y: pointer.y + length3 / 2 - (length3 - length4) - length4 + length4,
},
{
x: pointer.x - length1 / 2 + length1,
y: pointer.y + length3 / 2,
x: pointer.x - length1 / 2 + (length1 - length2) / 2 + length2,
y: pointer.y + length3 / 2 - (length3 - length4) - length4,
},
]
break
@ -673,25 +683,25 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
const topL = (length1 - length2) / 2 / Math.cos((angle * Math.PI) / 180) // 꺽이는부분 윗쪽 길이
points = [
{
x: pointer.x - length1 / 2 + length1,
y: pointer.y + length3 / 2,
},
{
x: pointer.x - length1 / 2,
y: pointer.y + length3 / 2,
},
{
x: pointer.x - length1 / 2 + length4 * Math.cos(degreesToRadians(angle)),
y: pointer.y + length3 / 2 - length4 * Math.sin(degreesToRadians(angle)),
x: pointer.x - length1 / 2 + length1,
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,
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)),
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)),
y: pointer.y + length3 / 2 - length4 * Math.sin(degreesToRadians(angle)),
},
]
break
@ -1066,45 +1076,294 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
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의 사본 가져오기
const newPoints = points.map((point) => {
let x = point.x
let y = point.y
// flipX 적용
if (flipX) {
x = width - x
}
// flipY 적용
if (flipY) {
y = height - y
}
// 스케일 및 전역 좌표 고려
x = (x - width / 2) * scaleX + width / 2
y = (y - height / 2) * scaleY + height / 2
return { x, y }
polygon.lines.forEach((line) => {
line.attributes.type = LINE_TYPE.WALLLINE.GABLE
})
// flipX, flipY를 초기화
polygon.flipX = false
polygon.flipY = false
const directionConfig = {
south: { evaesDirection: 'right', ridgeDirection: 'left', coord1: 'y1', coord2: 'y2' },
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 업데이트
polygon.set({ points: newPoints })
polygon.setCoords()
const { evaesDirection, ridgeDirection, coord1, coord2 } = directionConfig[polygon.direction] || directionConfig.west
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 {
@ -1115,5 +1374,6 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
changeSurfaceLinePropertyEvent,
changeSurfaceLineProperty,
changeSurfaceLinePropertyReset,
changeSurfaceLineType,
}
}

View File

@ -198,7 +198,7 @@ export function useCanvasEvent() {
if (selected?.length > 0) {
selected.forEach((obj) => {
if (obj.type === 'QPolygon' && obj.name !== 'module') {
if (obj.type === 'QPolygon') {
obj.set({ stroke: 'red' })
}
})
@ -235,7 +235,7 @@ export function useCanvasEvent() {
if (selected?.length > 0) {
selected.forEach((obj) => {
if (obj.type === 'QPolygon' && obj.name !== 'module') {
if (obj.type === 'QPolygon') {
obj.set({ stroke: 'red' })
}
})

View File

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

View File

@ -79,6 +79,9 @@ export function useEvent() {
// 마우스 위치 기준으로 확대/축소
canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom)
canvas.requestRenderAll()
canvas.calcOffset()
// 이벤트의 기본 동작 방지 (스크롤 방지)
opt.e.preventDefault()
opt.e.stopPropagation()
@ -104,6 +107,10 @@ export function useEvent() {
const horizonLines = canvas.getObjects().filter((obj) => obj.name === 'lineGrid' && obj.direction === 'horizontal')
const verticalLines = canvas.getObjects().filter((obj) => obj.name === 'lineGrid' && obj.direction === 'vertical')
if (!horizonLines || !verticalLines) {
return
}
const closestHorizontalLine = horizonLines.reduce((prev, curr) => {
const prevDistance = calculateDistance(pointer, prev)
const currDistance = calculateDistance(pointer, curr)

View File

@ -20,6 +20,7 @@ import { compasDegAtom } from '@/store/orientationAtom'
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
import { useCanvasPopupStatusController } from './common/useCanvasPopupStatusController'
import { useCanvasMenu } from './common/useCanvasMenu'
import { QcastContext } from '@/app/QcastProvider'
/**
* 플랜 처리
@ -52,6 +53,9 @@ export function usePlan(params = {}) {
const { fetchBasicSettings, basicSettingCopySave } = useCanvasSetting()
const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState)
/** 전역 로딩바 컨텍스트 */
const { setIsGlobalLoading } = useContext(QcastContext)
/**
* 플랜 복사 모듈이 있을경우 모듈 데이터 복사하기 위한 처리
*/
@ -414,7 +418,7 @@ export function usePlan(params = {}) {
useEffect(() => {
setSelectedPlan(currentCanvasPlan)
handleCurrentPlanUrl()
resetCurrentObject()
// resetCurrentObject()
resetModuleSetupSurface()
}, [currentCanvasPlan])
@ -450,13 +454,21 @@ export function usePlan(params = {}) {
text: `Plan ${currentCanvasPlan.planNo} ` + getMessage('plan.message.confirm.copy'),
type: 'confirm',
confirmFn: async () => {
setIsGlobalLoading(true)
await postObjectPlan(userId, objectNo, true, false)
setIsGlobalLoading(false)
},
denyFn: async () => {
setIsGlobalLoading(true)
await postObjectPlan(userId, objectNo, false, false)
setIsGlobalLoading(false)
},
})
: await postObjectPlan(userId, objectNo, false, false)
: async () => {
setIsGlobalLoading(true)
await postObjectPlan(userId, objectNo, false, false)
setIsGlobalLoading(false)
}
}
/**

View File

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

View File

@ -18,6 +18,7 @@
"plan.menu.placement.surface.initial.setting": "配置面初期設定",
"modal.placement.initial.setting.plan.drawing": "図面の作成方法",
"modal.placement.initial.setting.plan.drawing.size.stuff": "寸法入力による物件作成",
"modal.placement.initial.setting.plan.drawing.size.info": "※数字は[半角]入力のみ可能です。",
"modal.placement.initial.setting.size": "寸法入力方法",
"modal.placement.initial.setting.size.info": "寸法入力方法案内",
"modal.placement.initial.setting.size.roof": "伏図入力",
@ -36,8 +37,8 @@
"modal.roof.shape.setting.ridge": "棟",
"modal.roof.shape.setting.patten.a": "Aパターン",
"modal.roof.shape.setting.patten.b": "Bパターン",
"modal.roof.shape.setting.side": "別に設定",
"plan.menu.roof.cover": "屋根作図",
"modal.roof.shape.setting.side": "別に設定",
"plan.menu.roof.cover": "伏せ図入力",
"plan.menu.roof.cover.outline.drawing": "外壁線を描く",
"plan.menu.roof.cover.roof.shape.setting": "屋根形状の設定",
"plan.menu.roof.cover.roof.shape.passivity.setting": "屋根形状の手動設定",
@ -72,7 +73,7 @@
"common.setting.rollback": "前に戻る",
"modal.cover.outline.remove": "外壁の取り外し",
"modal.cover.outline.select.move": "外壁選択の移動",
"plan.menu.placement.surface": "配置面",
"plan.menu.placement.surface": "実測値入力",
"plan.menu.placement.surface.slope.setting": "傾斜設定",
"plan.menu.placement.surface.drawing": "配置面の描画",
"modal.placement.surface.drawing.straight.line": "直線",
@ -88,13 +89,17 @@
"plan.menu.module.circuit.setting.default": "モジュール/架台設定",
"modal.module.basic.setting.orientation.setting": "方位設定",
"modal.module.basic.setting.orientation.setting.info": "※シミュレーション計算用方位を指定します。南の方位を設定してください。",
"modal.module.basic.setting.orientation.setting.angle.passivity": "勾配を直接入力",
"modal.module.basic.setting.orientation.setting.angle.passivity": "角度変更",
"modal.module.basic.setting.module.roof.material": "屋根材",
"modal.module.basic.setting.module.trestle.maker": "架台メーカー",
"modal.module.basic.setting.module.rafter.margin": "垂木の間隔",
"modal.module.basic.setting.module.construction.method": "工法",
"modal.module.basic.setting.module.under.roof": "屋根の下",
"modal.module.basic.setting.module.setting": "モジュールの選択",
"modal.module.basic.setting.module.placement.area": "モジュール配置領域",
"modal.module.basic.setting.module.placement.area.eaves": "軒側",
"modal.module.basic.setting.module.placement.area.ridge": "棟側",
"modal.module.basic.setting.module.placement.area.keraba": "けらぱ",
"modal.module.basic.setting.module.hajebichi": "ハゼピッチ",
"modal.module.basic.setting.module.setting.info1": "※勾配の範囲には制限があります。屋根傾斜が2.5値未満10値を超える場合は、施工が可能かどうか施工マニュアルを確認してください。",
"modal.module.basic.setting.module.setting.info2": "※モジュール配置時は、施工マニュアルに記載されている<モジュール配置条件>を必ずご確認ください。",
@ -112,12 +117,16 @@
"modal.module.basic.setting.module.placement": "モジュールの配置",
"modal.module.basic.setting.module.placement.select.fitting.type": "設置形態を選択してください。",
"modal.module.basic.setting.module.placement.waterfowl.arrangement": "千鳥配置",
"modal.module.basic.setting.module.placement.max.row.amount": "Max単数",
"modal.module.basic.setting.module.placement.mix.max.row.amount": "混合Max単数",
"modal.module.basic.setting.module.placement.row.amount": "単数",
"modal.module.basic.setting.module.placement.column.amount": "熱水",
"modal.module.basic.setting.module.placement.do": "する",
"modal.module.basic.setting.module.placement.do.not": "しない",
"modal.module.basic.setting.module.placement.arrangement.standard": "配置基準",
"modal.module.basic.setting.module.placement.arrangement.standard.center": "中央",
"modal.module.basic.setting.module.placement.arrangement.standard.eaves": "軒",
"modal.module.basic.setting.module.placement.arrangement.standard.ridge": "棟",
"modal.module.basic.setting.module.placement.arrangement.standard.eaves": "軒",
"modal.module.basic.setting.module.placement.arrangement.standard.ridge": "棟",
"modal.module.basic.setting.module.placement.maximum": "最大配置",
"modal.module.basic.setting.pitch.module.placement.standard.setting": "配置基準設定",
"modal.module.basic.setting.pitch.module.placement.standard.setting.south": "南向き設置",
@ -128,9 +137,9 @@
"modal.module.basic.setting.pitch.module.row.margin": "上下間隔",
"modal.module.basic.setting.pitch.module.column.amount": "列数",
"modal.module.basic.setting.pitch.module.column.margin": "左右間隔",
"modal.module.basic.setting.prev": "移転",
"modal.module.basic.setting.prev": "前に戻る",
"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": "回路設定",
"modal.circuit.trestle.setting": "回路設定",
"modal.circuit.trestle.setting.alloc.trestle": "架台配置",
@ -178,7 +187,7 @@
"modal.roof.alloc.select.parallel": "筋配置",
"modal.roof.alloc.select.stairs": "千鳥配置",
"modal.roof.alloc.apply": "選択した屋根材として割り当て",
"plan.menu.estimate.docDown": "各種資料ダウンロード",
"plan.menu.estimate.docDownload": "見積書出力",
"plan.menu.estimate.save": "保存",
"plan.menu.estimate.reset": "初期化",
"plan.menu.estimate.copy": "見積書のコピー",
@ -558,7 +567,7 @@
"board.faq.title": "FAQ",
"board.faq.sub.title": "FAQリスト",
"board.archive.title": "各種資料ダウンロード",
"board.archive.sub.title": "見積書一覧",
"board.archive.sub.title": "掲載資料一覧",
"board.list.header.rownum": "番号",
"board.list.header.title": "タイトル",
"board.list.header.regDt": "登録日",
@ -594,6 +603,7 @@
"myinfo.message.password.error": "パスワードが間違っています。",
"login": "ログイン",
"login.auto.page.text": "自動ログイン中です。",
"login.fail": "アカウントが存在しないか、パスワードが間違っています。",
"login.id.save": "ID保存",
"login.id.placeholder": "IDを入力してください。",
"login.password.placeholder": "パスワードを入力してください。",
@ -841,7 +851,7 @@
"has.not.sleeve": "袖なし",
"jerkinhead.width": "半切妻幅",
"jerkinhead.slope": "半切妻傾斜",
"shed.width": "片流幅",
"shed.width": "片流れの出幅",
"windage": "片流れ",
"windage.width": "片流れの出幅",
"write": "作成",
@ -886,7 +896,7 @@
"estimate.detail.drawingEstimateCreateDate": "登録日",
"estimate.detail.lastEditDatetime": "変更日時",
"estimate.detail.saleStoreId": "一次販売店名",
"estimate.detail.estimateDate": "見積日",
"estimate.detail.estimateDate": "見積作成日",
"estimate.detail.otherSaleStoreId": "二次販売店名",
"estimate.detail.noOtherSaleStoreId": "二次店なし",
"estimate.detail.receiveUser": "担当者",
@ -895,6 +905,7 @@
"estimate.detail.estimateType": "注文分類",
"estimate.detail.estimateType.yjss": "住宅PKG",
"estimate.detail.estimateType.yjod": "積上げYJOD",
"estimate.detail.agency": "2次店名",
"estimate.detail.roofCns": "屋根材・施工区分",
"estimate.detail.remarks": "備考",
"estimate.detail.fileFlg": "後日資料提出",
@ -957,6 +968,7 @@
"estimate.detail.estimateCopyPopup.close": "閉じる",
"estimate.detail.estimateCopyPopup.copyBtn": "見積コピー",
"estimate.detail.estimateCopyPopup.copy.alertMessage": "見積書がコピーされました。コピーした見積情報に移動します。",
"estimate.detail.estimateCopyPopup.copy.alertMessageError": "キャンバスのコピー中にエラーが発生しました.",
"estimate.detail.productFeaturesPopup.title": "製品特異事項",
"estimate.detail.productFeaturesPopup.close": "閉じる",
"estimate.detail.productFeaturesPopup.requiredStoreId": "一次販売店は必須です。",
@ -997,7 +1009,7 @@
"simulator.table.sub5": "枚数",
"simulator.table.sub6": "合計",
"simulator.table.sub7": "パワーコンディショナー",
"simulator.table.sub8": "ティーン",
"simulator.table.sub8": "",
"simulator.table.sub9": "予測発電量kWh",
"simulator.notice.sub1": "ハンファジャパン年間発電量",
"simulator.notice.sub2": "シミュレーションガイド",
@ -1034,5 +1046,13 @@
"roof.exceed.count": "屋根材は4つまで選択可能です。",
"outerLine.property.fix": "外壁線の属性設定 を完了しますか?",
"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": "千鳥配置できない工法です。",
"module.layout.setup.max.count": "모듈의 최대 단수는 {0}, 최대 열수는 {1} 입니다. (JA)",
"module.layout.setup.max.count.multiple": "모듈 {0}번의 최대 단수는 {1}, 최대 열수는 {2} 입니다. (JA)",
"roofAllocation.not.found": "할당할 지붕이 없습니다. (JA)"
}

View File

@ -18,6 +18,7 @@
"plan.menu.placement.surface.initial.setting": "배치면 초기설정",
"modal.placement.initial.setting.plan.drawing": "도면 작성방법",
"modal.placement.initial.setting.plan.drawing.size.stuff": "치수 입력에 의한 물건 작성",
"modal.placement.initial.setting.plan.drawing.size.info": "※숫자는 [반각] 입력만 가능합니다.",
"modal.placement.initial.setting.size": "치수 입력방법",
"modal.placement.initial.setting.size.info": "치수 입력방법 안내",
"modal.placement.initial.setting.size.roof": "복시도 입력",
@ -88,13 +89,17 @@
"plan.menu.module.circuit.setting.default": "모듈/가대설정",
"modal.module.basic.setting.orientation.setting": "방위 설정",
"modal.module.basic.setting.orientation.setting.info": "※시뮬레이션 계산용 방위를 지정합니다. 남쪽의 방위를 설정해주세요.",
"modal.module.basic.setting.orientation.setting.angle.passivity": "각도를 직접 입력",
"modal.module.basic.setting.orientation.setting.angle.passivity": "각도 입력",
"modal.module.basic.setting.module.roof.material": "지붕재",
"modal.module.basic.setting.module.trestle.maker": "가대메이커",
"modal.module.basic.setting.module.rafter.margin": "서까래 간격",
"modal.module.basic.setting.module.construction.method": "공법",
"modal.module.basic.setting.module.under.roof": "지붕밑바탕",
"modal.module.basic.setting.module.setting": "모듈 선택",
"modal.module.basic.setting.module.placement.area": "모듈 배치 영역",
"modal.module.basic.setting.module.placement.area.eaves": "처마쪽",
"modal.module.basic.setting.module.placement.area.ridge": "용마루쪽",
"modal.module.basic.setting.module.placement.area.keraba": "케라바쪽",
"modal.module.basic.setting.module.hajebichi": "망둥어 피치",
"modal.module.basic.setting.module.setting.info1": "※ 구배의 범위에는 제한이 있습니다. 지붕경사가 2.5치 미만 10치를 초과하는 경우에는 시공이 가능한지 시공 매뉴얼을 확인해주십시오.",
"modal.module.basic.setting.module.setting.info2": "※ 모듈 배치 시에는 시공 매뉴얼에 기재된 <모듈 배치 조건>을 반드시 확인해주십시오.",
@ -112,12 +117,16 @@
"modal.module.basic.setting.module.placement": "모듈 배치",
"modal.module.basic.setting.module.placement.select.fitting.type": "설치형태를 선택합니다.",
"modal.module.basic.setting.module.placement.waterfowl.arrangement": "물떼새 배치",
"modal.module.basic.setting.module.placement.max.row.amount": "Max 단수",
"modal.module.basic.setting.module.placement.mix.max.row.amount": "혼합Max 단수",
"modal.module.basic.setting.module.placement.row.amount": "단수",
"modal.module.basic.setting.module.placement.column.amount": "열수",
"modal.module.basic.setting.module.placement.do": "한다",
"modal.module.basic.setting.module.placement.do.not": "하지 않는다",
"modal.module.basic.setting.module.placement.arrangement.standard": "배치 기준",
"modal.module.basic.setting.module.placement.arrangement.standard.center": "중앙",
"modal.module.basic.setting.module.placement.arrangement.standard.eaves": "처마",
"modal.module.basic.setting.module.placement.arrangement.standard.ridge": "용마루",
"modal.module.basic.setting.module.placement.arrangement.standard.eaves": "처마",
"modal.module.basic.setting.module.placement.arrangement.standard.ridge": "용마루",
"modal.module.basic.setting.module.placement.maximum": "최대배치",
"modal.module.basic.setting.pitch.module.placement.standard.setting": "배치기준 설정",
"modal.module.basic.setting.pitch.module.placement.standard.setting.south": "남향설치",
@ -129,6 +138,7 @@
"modal.module.basic.setting.pitch.module.column.amount": "열수",
"modal.module.basic.setting.pitch.module.column.margin": "좌우간격",
"modal.module.basic.setting.prev": "이전",
"modal.module.basic.setting.row.batch": "단수지정 배치",
"modal.module.basic.setting.passivity.placement": "수동 배치",
"modal.module.basic.setting.auto.placement": "설정값으로 자동 배치",
"plan.menu.module.circuit.setting.circuit.trestle.setting": "회로설정",
@ -178,7 +188,7 @@
"modal.roof.alloc.select.parallel": "병렬식",
"modal.roof.alloc.select.stairs": "계단식",
"modal.roof.alloc.apply": "선택한 지붕재로 할당",
"plan.menu.estimate.docDown": "문서 다운로드",
"plan.menu.estimate.docDownload": "문서 다운로드",
"plan.menu.estimate.save": "저장",
"plan.menu.estimate.reset": "초기화",
"plan.menu.estimate.copy": "견적서 복사",
@ -594,6 +604,7 @@
"myinfo.message.password.error": "비밀번호가 틀렸습니다.",
"login": "로그인",
"login.auto.page.text": "자동로그인 중 입니다.",
"login.fail": "계정이 없거나 비밀번호가 잘못되었습니다.",
"login.id.save": "ID Save",
"login.id.placeholder": "아이디를 입력해주세요.",
"login.password.placeholder": "비밀번호를 입력해주세요.",
@ -886,7 +897,7 @@
"estimate.detail.drawingEstimateCreateDate": "등록일",
"estimate.detail.lastEditDatetime": "변경일시",
"estimate.detail.saleStoreId": "1차 판매점명",
"estimate.detail.estimateDate": "견적일",
"estimate.detail.estimateDate": "견적작성일",
"estimate.detail.otherSaleStoreId": "2차 판매점명",
"estimate.detail.noOtherSaleStoreId": "2차점 없음",
"estimate.detail.receiveUser": "담당자",
@ -895,6 +906,7 @@
"estimate.detail.estimateType": "주문분류",
"estimate.detail.estimateType.yjss": "住宅PKG",
"estimate.detail.estimateType.yjod": "積上げ( YJOD )",
"estimate.detail.agency": "2차점명",
"estimate.detail.roofCns": "지붕재・사양시공",
"estimate.detail.remarks": "비고",
"estimate.detail.fileFlg": "후일자료제출",
@ -957,6 +969,7 @@
"estimate.detail.estimateCopyPopup.close": "닫기",
"estimate.detail.estimateCopyPopup.copyBtn": "견적복사",
"estimate.detail.estimateCopyPopup.copy.alertMessage": "견적서가 복사되었습니다. 복사된 물건정보로 이동합니다.",
"estimate.detail.estimateCopyPopup.copy.alertMessageError": "캔버스 복사 중 오류 발생.",
"estimate.detail.productFeaturesPopup.title": "제품특이사항",
"estimate.detail.productFeaturesPopup.close": "닫기",
"estimate.detail.productFeaturesPopup.requiredStoreId": "1차 판매점은 필수값 입니다.",
@ -1034,5 +1047,13 @@
"roof.exceed.count": "지붕재는 4개까지 선택 가능합니다.",
"outerLine.property.fix": "외벽선 속성 설정을 완료하시겠습니까?",
"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": "치조 불가 공법입니다.",
"module.layout.setup.max.count": "모듈의 최대 단수는 {0}, 최대 열수는 {1} 입니다.",
"module.layout.setup.max.count.multiple": "모듈 {0}번의 최대 단수는 {1}, 최대 열수는 {2} 입니다.",
"roofAllocation.not.found": "할당할 지붕이 없습니다."
}

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

@ -340,6 +340,14 @@
&.active{
top: calc(92.8px + 50px);
}
.canvas-id{
display: flex;
align-items: center;
padding: 9.6px 20px;
font-size: 12px;
color: #fff;
background-color: #1083E3;
}
.canvas-plane-wrap{
display: flex;
align-items: center;

View File

@ -24,6 +24,10 @@
.ag-header-cell{
font-size: 13px;
color: #fff;
border-right: 1px solid #738596;
&:last-child{
border: none;
}
}
.ag-header-cell-label{
justify-content: center;

View File

@ -279,10 +279,11 @@ $alert-color: #101010;
border-bottom: 1px solid #424242;
}
}
.grid-check-form-block{
display: block;
> div{
margin-bottom: 10px;
.grid-check-form-flex{
display: flex;
gap: 10px;
.d-check-box{
flex: 1;
}
}
.grid-option-overflow{
@ -1313,13 +1314,13 @@ $alert-color: #101010;
.circle {
position: absolute;
width: 12px;
height: 12px;
width: 10px;
height: 10px;
border: 1px solid #fff;
border-radius: 50%;
top: 95%;
top: 88%;
left: 50%;
transform-origin: 0 -90px; /* 중심에서 반지름 거리만큼 떨어져 위치 */
transform-origin: 0 -76px; /* 중심에서 반지름 거리만큼 떨어져 위치 */
cursor:pointer;
z-index: 3;
/* 0번을 180도 위치(아래)에, 13번을 0도 위치(위)에 배치 */
@ -1366,8 +1367,8 @@ $alert-color: #101010;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 5px;
height: 5px;
width: 4px;
height: 4px;
background-color: #fff;
border-radius: 50%;
}
@ -1381,15 +1382,15 @@ $alert-color: #101010;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 148px;
height: 148px;
width: 121px;
height: 121px;
border: 4px solid #fff;
border-radius: 50%;
.compas-arr{
width: 100%;
height: 100%;
background: url(../../public/static/images/canvas/compas.svg)no-repeat center;
background-size: 122px 122px;
background-size: 100px 100px;
}
}
}
@ -1441,10 +1442,10 @@ $alert-color: #101010;
.roof-module-compas{
margin-bottom: 24px;
.compas-box-inner{
width: 280px;
height: 253px;
width: 235px;
height: 215px;
.circle{
top: 86%;
top: 85%;
// &:nth-child(1),
// &:nth-child(7),
// &:nth-child(13),
@ -1461,7 +1462,7 @@ $alert-color: #101010;
// }
// }
i{
top: 22px;
top: 19px;
}
&.act{
i{color: #8B8B8B;}
@ -1482,6 +1483,10 @@ $alert-color: #101010;
.outline-form{
flex: 1;
}
.non-flex{
min-width: 300px;
flex: none;
}
}
.module-box-tab{
@ -2172,31 +2177,46 @@ $alert-color: #101010;
&.tab2{
margin-top: 10px;
gap: 15px;
.eaves-keraba-table{
margin-top: 0;
}
}
.module-flex-item{
flex: 1;
.module-flex-item-tit{
font-size: 12px;
font-weight: 500;
color: #fff;
padding-bottom: 10px;
border-bottom: 1px solid #4D4D4D;
}
.flex-item-btn-wrap{
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
margin-bottom: 24px;
margin-bottom: 10px;
}
&.non-flex{
display: flex;
flex-direction: column;
justify-content: flex-start;
flex: none;
padding-top: 27.5px;
width: 260px;
width: 340px;
}
.flex-item-button{
margin-top: 10px;
button{
width: 100%;
}
}
}
}
.module-flex-item-tit-wrap{
display: flex;
align-items: center;
padding-bottom: 10px;
border-bottom: 1px solid #4D4D4D;
.module-flex-item-tit{
font-size: 12px;
font-weight: 500;
color: #fff;
}
button{
margin-left: auto;
}
}
@ -2277,4 +2297,129 @@ $alert-color: #101010;
box-shadow: 0em -2.6em 0em 0em rgba(255, 255, 255, 0.2), 1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2), 2.5em 0em 0 0em rgba(255, 255, 255, 0.2), 1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2), 0em 2.5em 0 0em rgba(255, 255, 255, 0.2), -1.8em 1.8em 0 0em rgba(255, 255, 255, 0.5), -2.6em 0em 0 0em rgba(255, 255, 255, 0.7), -1.8em -1.8em 0 0em #fff;
}
}
}
.roof-module-inner{
display: flex;
.compas-wrapper{
position: relative;
flex: none;
width: 300px;
padding-right: 15px;
&:before{
content: '';
position: absolute;
top: 0;
right: 10px;
width: 1px;
height: 100%;
background-color: #424242;
}
}
.compas-table-wrap{
display: flex;
flex-direction: column;
flex: 1;
}
.compas-table-box{
background-color: #3D3D3D;
padding: 10px;
.outline-form{
span{
width: auto;
}
}
.compas-grid-table{
display: grid;
gap: 10px;
grid-template-columns: repeat(2, 1fr);
.outline-form{
span{
width: 65px;
&.thin{
width: 20px;
}
}
}
}
}
}
.module-table-block-wrap{
.roof-module-table{
&.self{
table{
table-layout: fixed;
}
}
}
.self-table-radio{
display: flex;
align-items: center;
justify-content: center;
}
}
.module-area{
display: flex;
align-items: center;
.module-area-title{
flex: none;
font-size: 12px;
color: #fff;
font-weight: 500;
margin-right: 20px;
}
.outline-form{
flex: 1;
}
}
.placement-name-guide{
font-size: 11px;
margin-left: 10px;
color: #53a7eb;
font-weight: 500;
}
.hexagonal-flex-wrap{
display: flex;
gap: 10px;
.non-flex{
flex: none;
}
}
.hexagonal-radio-wrap{
padding: 17px 10px;
}
.hide-check-guide{
display: flex;
align-items: center;
font-size: 12px;
color: #fff;
margin-top: 10px;
font-weight: 500;
.arr{
width: 13px;
height: 13px;
margin-left: 10px;
background: url(../../public/static/images/canvas/hide-check-arr.svg) no-repeat center;
background-size: contain;
transform: rotate(180deg);
&.act{
transform: rotate(0deg);
}
}
}
.module-table-box{
&.hide{
overflow: hidden;
height: 0;
}
}

View File

@ -222,15 +222,16 @@ button{
padding: 0 10px;
line-height: 28px;
font-family: 'Noto Sans JP', sans-serif;
background-color: transparent;
background-color: #353535;
border: 1px solid #484848;
color: #fff;
&.blue{
background-color: #4C6FBF;
border: 1px solid #4C6FBF;
&:hover{
background-color: #414E6C;
border: 1px solid #414E6C;
background-color: #4C6FBF;
border: 1px solid #4C6FBF;
font-weight: normal;
}
}
&.white{
@ -241,13 +242,14 @@ button{
background-color: #fff;
border: 1px solid #fff;
color: #101010;
font-weight: normal;
}
}
&:hover{
font-weight: 400;
background-color: transparent;
background-color: #353535;
border: 1px solid #484848;
color: #fff;
font-weight: normal;
}
}
&.self{

View File

@ -47,6 +47,16 @@ table{
}
.form-flex-wrap{
@include flexbox;
.form-flex-select{
@include flexbox;
label{
flex: none;
margin-right: 5px;
}
.form-select{
min-width: 300px;
}
}
}
.date-picker-wrap{
width: 100%;
@ -236,6 +246,16 @@ table{
.d-check-box{
opacity: 0.5;
}
.color-wrap{
display: flex;
align-items: center;
justify-content: center;
.color-box{
width: 14px;
height: 14px;
margin-right: 8px;
}
}
}
}
tbody{

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
const line1MinX = Math.min(line1.x1, line1.x2)
const line1MaxX = Math.max(line1.x1, line1.x2)
const line2MinX = Math.min(line2.x1, line2.x2)
const line2MaxX = Math.max(line2.x1, line2.x2)
const line1MinX = Math.min(line1.x1, line1.x2) - 5
const line1MaxX = Math.max(line1.x1, line1.x2) + 5
const line2MinX = Math.min(line2.x1, line2.x2) - 5
const line2MaxX = Math.max(line2.x1, line2.x2) + 5
const line1MinY = Math.min(line1.y1, line1.y2)
const line1MaxY = Math.max(line1.y1, line1.y2)
const line2MinY = Math.min(line2.y1, line2.y2)
const line2MaxY = Math.max(line2.y1, line2.y2)
const line1MinY = Math.min(line1.y1, line1.y2) - 5
const line1MaxY = Math.max(line1.y1, line1.y2) + 5
const line2MinY = Math.min(line2.y1, line2.y2) - 5
const line2MaxY = Math.max(line2.y1, line2.y2) + 5
// Check if the intersection X and Y are within the range of both lines
if (
@ -518,14 +518,23 @@ export const sortedPointLessEightPoint = (points) => {
*/
// 직선의 방정식.
// 방정식은 ax + by + c = 0이며, 점의 좌표를 대입하여 계산된 값은 직선과 점 사이의 관계를 나타낸다.
export function isPointOnLine(line, point) {
const a = line.y2 - line.y1
export function isPointOnLine({ x1, y1, x2, y2 }, { x, y }) {
/*const a = line.y2 - line.y1
const b = line.x1 - line.x2
const c = line.x2 * line.y1 - line.x1 * line.y2
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 찾기

View File

@ -305,6 +305,9 @@ export function removeDuplicatePolygons(polygons) {
}
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
}

2
startscript-3000.js Normal file
View File

@ -0,0 +1,2 @@
var exec = require('child_process').exec
exec('yarn dev -p 3000', { windowsHide: true })

View File

@ -1,2 +1,2 @@
var exec = require('child_process').exec
exec('yarn start', { windowsHide: true })
exec('yarn dev -p 5000', { windowsHide: true })