Compare commits

...

1216 Commits
main ... dev

Author SHA1 Message Date
c4b03c6745 diff 2로 수정 2026-01-09 17:28:02 +09:00
Jaeyoung Lee
1ccabd3efb Merge branch 'dev' into feature/design-remake 2026-01-09 17:15:59 +09:00
Jaeyoung Lee
9369c1c952 qline 실치수 표시 부분 수정 2026-01-09 17:15:18 +09:00
5a73b3787f 시작 전 quotationParam null 세팅 추가 2026-01-09 15:33:04 +09:00
04f06702a8 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev_ysCha 2026-01-09 10:34:17 +09:00
ed63cc664c YJSS 숨김처리 2026-01-09 10:33:49 +09:00
af90e0fa36 오류 수정 2026-01-09 09:53:40 +09:00
6a276c4bd7 Merge pull request '한글 -> 일어변경' (#564) from dev_ysCha into dev
Reviewed-on: #564
2026-01-08 19:03:38 +09:00
174a31aa72 한글 -> 일어변경 2026-01-08 19:02:48 +09:00
0edc7bd161 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev_ysCha 2026-01-08 18:12:30 +09:00
de707796a3 더블계산패드노출 2026-01-08 18:12:10 +09:00
fbb1f352e9 벽 라인 wallLine 속성 변경으로 수정 2026-01-08 17:03:14 +09:00
10bdc6e8ab Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2026-01-08 16:41:18 +09:00
8d5d36fce6 실치수 계산 시 소수점 적용 문제로 인한 수정 모든 roof lines를 확인하여 length 통일 2026-01-08 16:41:11 +09:00
916a6caf5a Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev_ysCha 2026-01-08 15:18:07 +09:00
12f442b3ed 반영 변경 2026-01-08 15:17:45 +09:00
9683b38cf0 방향키에 숫자패드 숨김 2026-01-08 15:15:17 +09:00
49253b7668 1390 【HANASYS DESIGN】복도에서 작성한 경우 치수 취급 2026-01-08 15:04:00 +09:00
a258f61093 탭키, 방향키 계산 2026-01-08 14:46:21 +09:00
ddd3eaf82a 치수 표시 선 1의 자리 0으로 나오는 현상 수정 2026-01-08 12:54:00 +09:00
3a3afe9b3b 실내집중형 북면모듈 오류 수정 2026-01-08 10:26:24 +09:00
4651ebc365 초기화 2026-01-08 10:15:44 +09:00
861fd31825 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev_ysCha 2026-01-07 17:52:37 +09:00
a5dc5caaf3 각도계산 초기화 2026-01-07 17:51:11 +09:00
47de1ef61d azimuth: string -> int 계산식 2026-01-07 17:49:37 +09:00
c499653e79 북면 모듈 설치 여부 추가, 북면 모듈 설치 시 동면 제외 2026-01-07 16:59:30 +09:00
5ac023d72d [HANASYS DESIGN] 회전 기능을 이용했을 때의 불량 2026-01-07 10:35:24 +09:00
6872c6bb16 배치면 그리기, 외벽선 그리기 각도 문제 해결 2026-01-07 10:12:25 +09:00
bff666914c font 설정 오류 수정 2026-01-07 10:07:31 +09:00
cdec0bbae0 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev_ysCha 2026-01-06 17:49:06 +09:00
47078a2205 maxHeight 추가 2026-01-06 17:47:30 +09:00
c8d0dd30e5 폰트 설정 오류 수정 2026-01-06 16:50:48 +09:00
2b29a62616 방위각 제한 (-180 ~ 180) : string 제한 2026-01-06 14:39:18 +09:00
72e4aded51 계산기패드 초기화안되는 문제 2026-01-06 13:46:14 +09:00
a128b0b5bb 방위각 제한 (-180 ~ 180) 2026-01-06 12:22:57 +09:00
295694ee82 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev_ysCha 2026-01-05 19:00:04 +09:00
faa619d124 메인에서 새로고침 chagePasswordPopOpen 2026-01-05 18:59:43 +09:00
Jaeyoung Lee
eec659257a Merge branch 'dev' into feature/design-remake
# Conflicts:
#	src/common/common.js
#	src/hooks/roofcover/useRoofAllocationSetting.js
2026-01-05 15:15:02 +09:00
Jaeyoung Lee
8f421d08da 변별로 처리 시 용마루 하단지붕 파생 라인 처리 및 불필요 코드 제거 2026-01-05 15:11:53 +09:00
a74789d8e8 회전 후 잘리는 현상 수정 2026-01-02 14:37:08 +09:00
fd8cbf726c 할당처리 2025-12-31 15:41:46 +09:00
7557f61130 북면 설치 가능 모듈은 단독으로만 가능함 2025-12-31 14:17:45 +09:00
1524766b7e 외벽선 없는 경우 처리 추가 2025-12-31 13:52:50 +09:00
Jaeyoung Lee
5131e20a5c Merge branch 'feature/design-remake' into dev 2025-12-31 13:19:32 +09:00
Jaeyoung Lee
5bbf372e47 지붕 보조선 버그 수정 2025-12-31 13:19:12 +09:00
9e867ba53a 단 이동 시 복사로 표기되는 오류 수정 2025-12-31 10:42:46 +09:00
1889c58d0f Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-12-31 10:42:38 +09:00
f0e70ec2df 단 이동 시 복사로 표기되는 오류 수정 2025-12-31 10:42:00 +09:00
d25ab38f11 getAddLine(newPStart, newPEnd, 'red') 이동 - 동시변경 2025-12-31 10:05:51 +09:00
2c7dcbc57d 수동 회로 설정 로직 수정 2025-12-30 17:01:24 +09:00
9cdb7ebd0f session의 StoreId => 1,2차점 storeId 2025-12-30 14:24:41 +09:00
008a1230cc wallbaseLine merge 2025-12-30 10:57:53 +09:00
Jaeyoung Lee
f0e71df5d4 Merge branch 'dev' into feature/design-remake 2025-12-30 10:16:30 +09:00
Jaeyoung Lee
c78d3ecf6b 용마루 파생 하단 지붕 처리 중 임시 저장 2025-12-30 10:16:02 +09:00
b9efc4aa47 모듈 설치 시 validation 추가 2025-12-29 16:56:39 +09:00
bbd3d1ec4b 모듈 복사, 제거, 추가 후 선택 안되는 현상 수정 2025-12-29 14:51:55 +09:00
953ce7535d 외주 이격 설정 오류 수정 2025-12-29 11:44:08 +09:00
c454b1936f 수동설정 시 실내집중형 로직 수정 2025-12-29 11:10:02 +09:00
54767d2f26 northModuleYn 컬럼 추가 2025-12-24 17:27:50 +09:00
c4bcad3bc3 보조선 있는 경우 지붕선 사라지는 현상 수정 2025-12-24 15:10:42 +09:00
984428ee4f Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev_ysCha 2025-12-24 15:05:42 +09:00
152bcb6a5b 자리수12자리 2025-12-24 15:05:17 +09:00
c1e4fe5b10 row 선택 시 병렬 수 0으로 가는 현상 수정 2025-12-24 15:00:15 +09:00
15d94bab53 abs제거 2025-12-24 14:24:30 +09:00
a6dff928c0 pcs최초 데이터 참조 수정 2025-12-24 14:20:22 +09:00
456ec7643e 전체삭제 2025-12-24 14:13:30 +09:00
c8ad779c58 최초 pcs 관련 데이터 참조하도록 수정 2025-12-24 14:05:09 +09:00
92a3b15d40 onClick event 수정 2025-12-24 13:26:55 +09:00
ae1ae05d09 회로 설정 수정 2025-12-24 09:50:47 +09:00
cc5734db16 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-12-24 09:40:46 +09:00
dc080a8737 견적서에 계산기추가 2025-12-23 15:30:27 +09:00
59b65df1d6 abs() 수정 2025-12-23 15:21:29 +09:00
0ee7f6c590 계산기 2025-12-23 14:19:35 +09:00
3f9bc960b3 좌표삭제 2025-12-23 13:31:38 +09:00
b02faafb3e 도머, 그림자, 개구 이격 30으로 수정 2025-12-23 10:17:58 +09:00
f8cc93d54e 할당완료 2025-12-22 18:20:54 +09:00
54ac757ced 계산기 투입 2025-12-22 17:54:07 +09:00
2e3ae74a82 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev_ysCha 2025-12-22 17:33:44 +09:00
6fdb0df00c 0.이하 처리 2025-12-22 17:31:45 +09:00
Jaeyoung Lee
83769c55ab Merge branch 'feature/design-remake' into dev 2025-12-22 14:45:58 +09:00
Jaeyoung Lee
8f1874c3cd 하단레벨 지붕선 작성로직 B타입 추가. 2025-12-22 14:45:23 +09:00
ab44be17fd 특이사항 제거 2025-12-22 13:19:09 +09:00
e3ce0dea3f 오브젝트 등록수정 2025-12-22 10:50:43 +09:00
8289f7feef skt 변경 2025-12-21 00:15:18 +09:00
e958f2ee4d 마이너스 좌표 비교 2025-12-21 00:06:45 +09:00
Jaeyoung Lee
8c92f14f7d Merge branch 'feature/design-remake' into dev 2025-12-19 17:36:15 +09:00
Jaeyoung Lee
494b06bfe3 하단지붕선 작성 로직 추가. 2025-12-19 17:35:53 +09:00
Jaeyoung Lee
de005aefe7 Merge branch 'feature/design-remake' into dev 2025-12-19 14:19:21 +09:00
Jaeyoung Lee
fd024cf30b 케라바 벽취합 조합에서의 버그 처리. 2025-12-19 14:17:07 +09:00
67ebf0b514 [1360] 일본어 2025-12-19 13:12:41 +09:00
8be85a3f9e skt 2025121901 2025-12-19 09:19:26 +09:00
e93ecf169d Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev_ysCha 2025-12-19 08:55:58 +09:00
306c71ead2 동이동변경 2025-12-18 18:05:08 +09:00
50376446d0 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-12-18 17:59:25 +09:00
1a432913fc A,B타입 지붕재 할당 시 방향 오류 수정 2025-12-18 17:59:16 +09:00
7be6b60e12 동이동변경 2025-12-18 17:27:28 +09:00
abb498178d Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev_ysCha 2025-12-18 17:19:21 +09:00
618604b52c 가대설정: 계산기 추가 2025-12-18 16:17:28 +09:00
f202cfd33e 모듈시공: 계산기 추가 2025-12-18 16:14:31 +09:00
86852cc4cd 방향모듈설정: 계산기 추가 2025-12-18 14:32:16 +09:00
b6bfa8fd2e Merge pull request '오브젝트 저장: 계산기 추가' (#497) from dev_ysCha into dev
Reviewed-on: #497
2025-12-18 14:06:06 +09:00
03a629b9c8 오브젝트 저장: 계산기 추가 2025-12-18 13:40:08 +09:00
Jaeyoung Lee
252782f275 Merge branch 'feature/design-remake' into dev 2025-12-18 11:28:16 +09:00
Jaeyoung Lee
013fd963b3 변별로 4각에서 마루선 그려지지 않는 경우 처리 2025-12-18 11:27:56 +09:00
f345b0dbbe 처마/케라바 변경 탭 변경시 버튼 값 초기화 2025-12-18 10:12:34 +09:00
Jaeyoung Lee
86d595ef52 주석처리 안된곳 처리. 2025-12-17 17:50:18 +09:00
Jaeyoung Lee
ce5192ba1a Merge branch 'dev' into feature/design-remake 2025-12-17 17:34:56 +09:00
Jaeyoung Lee
3a8769bd91 변별로 설정 1차 운영서버 반영 2025-12-17 17:33:16 +09:00
5e93c4d0e8 qna_고객명노출 2025-12-17 16:07:51 +09:00
e7d83b727a 동->현이동 2025-12-17 15:51:31 +09:00
d096aae6db skt컨텐츠메뉴노출 2025-12-17 15:37:37 +09:00
eb71af3799 중복제거 2025-12-17 15:36:36 +09:00
e9b6624cb7 Merge pull request 'dev_ysCha' (#488) from dev_ysCha into dev
Reviewed-on: #488
2025-12-16 18:35:56 +09:00
7bf2db7479 동이동현이동 : 계산기 추가 2025-12-16 17:58:46 +09:00
1ad47291f2 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-12-16 17:52:48 +09:00
8eeff43b4c 지붕재 할당 전 지붕 옮길 시 outerLinePoints 좌표도 수정 필요 2025-12-16 17:52:41 +09:00
6f47b9dc54 면형상배치 : 계산기 추가 2025-12-16 17:39:28 +09:00
b81fc389f5 보조선이동복사 : 계산기 추가 2025-12-16 17:20:28 +09:00
bbf2dfbe7c 보조선사이즈변경 : 계산기 추가 2025-12-16 17:14:47 +09:00
4f483dbfee 외벽편집오브셋 : 계산기 추가 2025-12-16 16:20:26 +09:00
19bdaa52fd 보조선로만들기 : 계산기 추가 2025-12-16 16:07:18 +09:00
f9f29223e4 개구 : 계산기 추가 2025-12-16 15:49:40 +09:00
b76f6eb057 [1346]문의게시판 : cnustNm 추가 2025-12-16 14:12:44 +09:00
b0b87bbd66 지붕면 이동 후 선택 제대로 안되는 현상 수정, 지붕면 할당 시 방향 화살표 전부 남쪽으로 되는 현상 수정 2025-12-16 13:34:52 +09:00
4a959ba8cf skeleton open 2025-12-16 11:19:54 +09:00
33fa60360b Merge pull request 'skeleton open' (#482) from dev_ysCha into dev
Reviewed-on: #482
2025-12-16 11:10:08 +09:00
fdeb0dab9a skeleton open 2025-12-16 11:09:05 +09:00
01a34fb8bc Merge pull request '[1351]경사도 기본 수치 추가 inclBase' (#479) from dev_ysCha into dev
Reviewed-on: #479
2025-12-16 10:46:12 +09:00
b35ae4407a [1351]경사도 기본 수치 추가 inclBase 2025-12-16 10:44:43 +09:00
cbd80d0602 Merge pull request 'skt수정2' (#476) from dev_ysCha into dev
Reviewed-on: #476
2025-12-15 18:22:50 +09:00
b4a7bb2e48 skt수정2 2025-12-15 18:21:40 +09:00
11e5ade5bd Merge pull request 'skt수정' (#473) from dev_ysCha into dev
Reviewed-on: #473
2025-12-15 17:58:31 +09:00
4eb89068c5 skt수정 2025-12-15 17:58:00 +09:00
9a37959b60 Merge pull request '[1351]경사도 기본 수치 추가 inclBase' (#471) from dev_ysCha into dev
Reviewed-on: #471
2025-12-15 16:44:05 +09:00
ff5cd04b27 [1351]경사도 기본 수치 추가 inclBase 2025-12-15 16:42:31 +09:00
39bf5f5ccc Merge pull request 'followLine 수정' (#468) from dev_ysCha into dev
Reviewed-on: #468
2025-12-15 14:25:50 +09:00
9d9cf4a05d followLine 수정 2025-12-15 14:24:15 +09:00
25c7037e86 Merge pull request 'out 완료' (#465) from dev_cha into dev
Reviewed-on: #465
2025-12-15 01:31:08 +09:00
6c5a0a8a54 out 완료 2025-12-15 01:29:04 +09:00
f9b63bca42 Merge pull request '할당완료' (#462) from dev_cha into dev
Reviewed-on: #462
2025-12-13 17:05:49 +09:00
be6fdce63c 할당완료 2025-12-13 17:04:59 +09:00
53a1f4ed00 group object 좌표 재계산 로직 수정 2025-12-12 15:27:26 +09:00
97a444bccd Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-12-12 15:20:36 +09:00
a6e935bc15 도머 오프셋 선택 시 오류 수정 2025-12-12 15:20:28 +09:00
Jaeyoung Lee
f3cd10931d 변별로 처리 중간저장 2025-12-11 14:39:46 +09:00
f898b4434b Merge pull request 'sortRoofLines 개선' (#460) from dev_cha into dev
Reviewed-on: #460
2025-12-11 01:51:43 +09:00
fef352933f sortRoofLines 개선 2025-12-11 01:51:04 +09:00
ab78399e42 Merge pull request '시뮬레이션 수정' (#458) from dev_ysCha into dev
Reviewed-on: #458
2025-12-10 11:21:20 +09:00
2205809d0b 시뮬레이션 수정 2025-12-10 11:20:10 +09:00
c5d830f6c0 플랜 이동 시 외벽선 남아있는 현상 수정
지붕재 변경 시 같은 지붕재를 가지고 있는 다른 지붕들의 지붕재도 변경 추가
2025-12-10 10:24:37 +09:00
16870adc30 Merge pull request 'sortedBaseLines' (#456) from dev_cha into dev
Reviewed-on: #456
2025-12-10 00:41:57 +09:00
540812861d sortedBaseLines 2025-12-10 00:40:53 +09:00
5ca4f3d901 Merge pull request 'dev_ysCha' (#454) from dev_ysCha into dev
Reviewed-on: #454
2025-12-09 10:41:53 +09:00
2625dcdd08 절단변경 2025-12-09 10:40:51 +09:00
d0156bf838 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev_ysCha 2025-12-09 10:39:36 +09:00
559c085183 Merge pull request '동이동 추가' (#452) from dev_cha into dev
Reviewed-on: #452
2025-12-09 00:16:42 +09:00
a8c7d05278 동이동 추가 2025-12-09 00:10:40 +09:00
f5e315eb03 Merge pull request 'Merge pull request '좌표삭제, 라인사이즈 줄임' (#450) from dev_ysCha into dev' (#451) from dev into dev_ysCha
Reviewed-on: #451
2025-12-08 10:32:50 +09:00
4a0eb66a3c Merge pull request '좌표삭제, 라인사이즈 줄임' (#450) from dev_ysCha into dev
Reviewed-on: #450
2025-12-08 10:32:16 +09:00
5da154df5d 좌표삭제, 라인사이즈 줄임 2025-12-08 10:31:32 +09:00
d7fa196570 Merge pull request 'v0.3' (#448) from dev_cha into dev
Reviewed-on: #448
2025-12-07 21:35:24 +09:00
2220e2ea08 v0.3 2025-12-07 16:05:28 +09:00
23b8aa7fa7 Merge pull request 'dev_cha' (#447) from dev_cha into dev
Reviewed-on: #447
2025-12-07 14:51:30 +09:00
acde55f4d1 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev_cha 2025-12-07 14:49:14 +09:00
dbb6f0af81 동이동 및 fabric 버전업 2025-12-07 14:48:05 +09:00
8943ab2f38 v0.2 2025-12-07 13:54:45 +09:00
28d26511de 복사 후 문제 수정 2025-12-05 15:36:56 +09:00
eee74edf9e 외벽선 표시 추가 2025-12-04 17:13:34 +09:00
800453ff6b 혼합모듈 회로 대응 작업 추가 2025-12-04 14:28:30 +09:00
eb315cae64 style: 회로설정 팝업 총 회로수 select-box 추가 2025-12-04 10:51:40 +09:00
bf681dccba 저장 시 줌 관련 문제 수정 2025-12-02 11:05:03 +09:00
1fff48bbfa Merge pull request 'canvas 삭제' (#443) from dev_ysCha into dev
Reviewed-on: #443
2025-12-01 11:25:21 +09:00
3c92c2b8d7 canvas 삭제 2025-12-01 11:24:47 +09:00
8fa207a329 Merge pull request 'dev_ysCha' (#442) from dev_ysCha into dev
Reviewed-on: #442
2025-12-01 11:17:02 +09:00
ed9379081c Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev_ysCha
# Conflicts:
#	src/components/floor-plan/modal/object/SizeSetting.jsx
2025-12-01 11:15:22 +09:00
5a1dcc463c 게산기 수정 2025-12-01 11:07:39 +09:00
ee33558f2f 개구, 오브젝트 설치 후 길이 안나오는 현상 수정 2025-11-28 15:16:16 +09:00
6800fb035a Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-11-28 11:22:53 +09:00
3d1e3d2abd 회로설정 수동설정 창크기 조절 관련 수정 2025-11-28 11:22:46 +09:00
d747aee284 Merge pull request 'dev_ysCha' (#436) from dev_ysCha into dev
Reviewed-on: #436
2025-11-27 10:34:28 +09:00
9c2619b99e 실측값입력 2025-11-27 10:33:47 +09:00
924ec70402 실측값입력 2025-11-27 10:28:50 +09:00
902c6c1eaf Merge pull request 'ref={pitchRef}' (#433) from dev_ysCha into dev
Reviewed-on: #433
2025-11-26 18:11:46 +09:00
5bb49097ef ref={pitchRef} 2025-11-26 18:10:48 +09:00
189dd82c50 모듈, 회로구성 팝업 줄이기 추가 2025-11-26 09:47:06 +09:00
c2114efe0e Merge pull request 'dev_ysCha' (#430) from dev_ysCha into dev
Reviewed-on: #430
2025-11-26 09:30:20 +09:00
34871397ee [1308] 촌에 소수점 입력 - {pitchText} 2025-11-26 09:29:17 +09:00
aeb457e5ec [1308] 촌에 소수점 입력 - 처마,케라바 수정 2025-11-26 09:18:01 +09:00
7ddbc40cd4 필요하지 않은 접힘 버튼 제거 2025-11-25 13:41:35 +09:00
97209b6742 Merge pull request 'sessionState?.storeId' (#425) from dev_ysCha into dev
Reviewed-on: #425
2025-11-25 13:33:28 +09:00
f5b68894ea sessionState?.storeId 2025-11-25 13:32:36 +09:00
b2799dacb8 Merge pull request '[1308] 촌에 소수점 입력 - 처마,케라바 수정' (#422) from dev_ysCha into dev
Reviewed-on: #422
2025-11-25 09:49:57 +09:00
d9b68f3012 [1308] 촌에 소수점 입력 - 처마,케라바 수정 2025-11-25 09:49:04 +09:00
6d56701483 Merge pull request '[1308] 촌에 소수점 입력2' (#419) from dev_ysCha into dev
Reviewed-on: #419
2025-11-24 14:24:45 +09:00
0a97166b06 [1308] 촌에 소수점 입력2 2025-11-24 14:23:45 +09:00
3d0df7bc58 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev_ysCha 2025-11-24 14:07:07 +09:00
3071c0ddc5 [1308] 촌에 소수점 입력 2025-11-24 14:06:01 +09:00
43f70f9f79 소수점이하 2자리 추가 2025-11-24 14:03:10 +09:00
ed28871a5b Merge branch 'qcast-pub' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-11-24 13:56:28 +09:00
Jaeyoung Lee
6c0cc73cb8 중간 저장(반박공지붕 추가) 2025-11-24 10:35:00 +09:00
Jaeyoung Lee
a201d65ebe 변별로 대응 중간 저장 2025-11-21 14:15:52 +09:00
101e086a84 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev_ysCha 2025-11-18 13:21:10 +09:00
f63202877f [1314][HANASYS DESIGN]Simulation내용의 변경에 대해서 2025-11-18 13:18:38 +09:00
Jaeyoung Lee
d148077e6b 변별로 설정 중간 저장 2025-11-12 10:40:57 +09:00
e5551ea0e5 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into qcast-pub 2025-11-11 17:39:46 +09:00
9af16ac047 수동지붕설정 제대로 생성 안되는 현상 수정 2025-11-11 17:20:44 +09:00
2b8de3d0d0 style: 모달 접기 버튼 추가 2025-11-07 15:46:04 +09:00
cacefcc5d0 Merge remote-tracking branch 'origin/dev_ysCha' into dev_ysCha 2025-11-07 09:02:56 +09:00
88632a4619 sk제거 2025-11-07 08:45:51 +09:00
575386c526 Merge pull request '[1308] 배치면 초기 설정의 지붕 경사(경사도)에서 소수점 입력이 불가능합니다' (#412) from dev into dev_ysCha
Reviewed-on: #412
2025-11-07 08:37:15 +09:00
3c094b3c5a log 제거 2025-11-07 08:32:41 +09:00
97725c1a3d Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev_ysCha
# Conflicts:
#	src/util/skeleton-utils.js
2025-11-07 08:14:28 +09:00
7377b06e2a log 제거 2025-11-07 07:57:41 +09:00
Cha
ee79c0e680 알고리즘 변경 2025-11-06 23:06:35 +09:00
Cha
a3d1704390 baseLine수정 2025-11-02 00:51:49 +09:00
Cha
fecc4e999a baseLine수정 2025-11-02 00:48:53 +09:00
34a1a6d201 Merge pull request 'log 제거' (#408) from dev_ysCha into dev
Reviewed-on: #408
2025-10-31 18:09:13 +09:00
03c31c82ff log 제거 2025-10-31 18:08:23 +09:00
978d74f66a Merge pull request 'dev_ysCha' (#406) from dev_ysCha into dev
Reviewed-on: #406
2025-10-30 16:39:40 +09:00
990c0e7e29 log 제거 2025-10-30 16:38:48 +09:00
a428cc31e8 초기화 변경 2025-10-30 16:36:53 +09:00
baeb2be0a1 동,현이동 동일라인체크 변경, 상우선택시 초기화 문제 2025-10-30 16:35:42 +09:00
89b8af1533 Merge pull request '현의 상(out), 하(in) 의미 변경' (#404) from dev_ysCha into dev
Reviewed-on: #404
2025-10-29 16:03:55 +09:00
a75ea4c98a 현의 상(out), 하(in) 의미 변경 2025-10-29 16:02:28 +09:00
4a716ad7ad Merge pull request 'Q.PARTNERS 링크 추가' (#401) from dev_ysCha into dev
Reviewed-on: #401
2025-10-28 16:40:46 +09:00
8cd67a92a8 Q.PARTNERS 링크 추가 2025-10-28 16:39:17 +09:00
befa12b00b 길이 위치 수정 2025-10-27 14:38:25 +09:00
d7e35dba40 Merge pull request 'target 의 좌표비교 변경 Math.abs(target.y1 - target.y2) < 0.2' (#400) from dev_ysCha into dev
Reviewed-on: #400
2025-10-27 13:19:34 +09:00
2080c8bf20 target 의 좌표비교 변경 Math.abs(target.y1 - target.y2) < 0.2 2025-10-27 13:17:56 +09:00
affef782f3 roof.moveFlowLine 수정 2025-10-24 18:36:38 +09:00
0ad18e4f15 roof.moveFlowLine 수정, log 제거 2025-10-24 13:04:56 +09:00
82698a5d03 동,현이동 동일라인체크 변경, 상우선택시 초기화 문제 2025-10-23 18:34:11 +09:00
7cc20f33c6 동,현이동 동일라인체크 변경, 상우선택시 초기화 문제 2025-10-22 17:28:16 +09:00
7baa198501 [1258] api/v1/master/pcsMakerList 에 moduleMatlCds 파라미터 추가 2025-10-22 13:36:37 +09:00
396d8bc708 모듈시리즈 최초 전체 선택 2025-10-22 10:05:38 +09:00
c60da7ddc9 [1305]Re.RISE-NBC AG270 모듈을 북면 설치했을 때 북면 설치용 첨부자료를 불필요하게 만들고 싶다. 2025-10-21 10:50:41 +09:00
a2cd08484d skeleton 2025-10-17 18:57:47 +09:00
3a3ff7c156 보조라인 길이 나오지 않음 => obj.name !== 'lengthText' && 추가 2025-10-16 18:04:09 +09:00
d548b0e1f4 360도 회전시 위치 수정 2025-10-15 16:15:21 +09:00
ddf326ca6b 동, 현이동 확인항목 추가 2025-10-15 09:54:13 +09:00
8b2cf6a9d3 시계방향 대응 추가 2025-10-14 14:16:02 +09:00
41de001986 순서에 따라 abs적용 추가 2025-10-14 13:58:15 +09:00
1385683bce hipSize 간혹 오류발생 - 기본값 0 설정 2025-10-14 11:05:50 +09:00
92fd17ed71 문의게시판 저장수정 글자변경 2025-10-14 10:59:10 +09:00
320080e0c1 문의게시판 상세 api 수정 2025-10-01 11:09:34 +09:00
d32553416c [1309] 문의사항 3 카테고리 문제 2025-10-01 10:14:15 +09:00
1aa6bc79a8 견적서 및 발전량에 페이지에서 canvas가 null임.. 2025-09-30 18:19:16 +09:00
b480345b24 [1222]견적의 제품 정보 화면에 대해서 - 그룹별 추가 2025-09-30 18:12:25 +09:00
5f726bf5db input 계산기추가(전각) 2025-09-30 14:25:31 +09:00
9a2c6adb96 [big.js] Division by zero - actualSize 가 0이 되어 나누기에 문제가 됨 2025-09-30 14:22:59 +09:00
75312f5ccf 스켈레톤.v0.1 2025-09-30 13:41:34 +09:00
ba94dd0579 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev_ysCha 2025-09-30 13:29:38 +09:00
384c68c1ef [big.js] Division by zero - actualSize 가 0이 되어 나누기에 문제가 됨 2025-09-30 13:29:09 +09:00
dfed51a758 각도 계산식 소수점 둘째자리까지 표시하도록 수정 2025-09-30 09:55:01 +09:00
01f160fa8b Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev_ysCha 2025-09-29 11:38:33 +09:00
c58c1f2106 [1195] DESIGN 발전 시뮬레이션 색상 변경 2025-09-29 11:37:28 +09:00
Jaeyoung Lee
bf0e1e4cb0 코드 정리 2025-09-29 10:26:22 +09:00
Jaeyoung Lee
340c7669af Merge branch 'dev' into feature/design-remake 2025-09-29 10:22:08 +09:00
Jaeyoung Lee
8cca1e9937 BackwardLines 수정 2025-09-29 10:21:45 +09:00
e8adf1659f Merge pull request '[1306] 가대 설정의 불량' (#361) from dev_ysCha into dev
Reviewed-on: #361
2025-09-26 18:07:43 +09:00
9786c1fbf7 [1306] 가대 설정의 불량 2025-09-26 18:01:08 +09:00
4469691618 [1237] 모듈 자동 선택 문제 2025-09-26 10:57:03 +09:00
9c0403c947 trestleState.constTp === data.constTp), 2025-09-25 10:54:50 +09:00
4df39defc6 next trestleState.constTp 2025-09-25 10:50:02 +09:00
0efc90f135 [1237] 모듈/가대 목록 - timeout 연장 2025-09-24 13:03:18 +09:00
Jaeyoung Lee
bbd8a43864 innerLines 초기화 추가 2025-09-24 10:48:10 +09:00
Jaeyoung Lee
46710533b5 동선이동 수정 2025-09-24 10:46:36 +09:00
eeab13b9cd 초기ALL문제 해결 2025-09-24 10:02:52 +09:00
0acd9e422f skeleton 변경 2025-09-23 18:40:14 +09:00
Jaeyoung Lee
c58146ca53 소수점 문제로 인한 지붕선 찾기 오류 처리 2025-09-22 10:40:35 +09:00
960d31ffdd Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-09-22 09:59:53 +09:00
93e54812c3 배치면 선택 시 기존 roof stroke, strokeWidth 수정 2025-09-22 09:59:35 +09:00
816e440ba0 skeleton 변경 2025-09-19 18:02:24 +09:00
a8d9988f24 지붕 회전 시 내부 오브젝트(개구, 도머, 그림자) 회전 반영 2025-09-19 10:19:49 +09:00
21943536c9 skeleton 20% 2025-09-16 18:19:13 +09:00
Cha
99c7759e00 skeleton-utils 2025-09-16 00:14:03 +09:00
7d9b6d5225 object 회전 기능 추가 2025-09-15 14:55:04 +09:00
Cha
36d16069e0 skeleton-utils 2025-09-14 01:09:03 +09:00
f912a8474e fetching error: TypeError: Cannot read properties of undefined (reading 'planNo')
at eval (useRoofAllocationSetting.js:179:26)
    at async fetchBasicSettings (useRoofAllocationSetting.js:112:7)
2025-09-12 16:45:53 +09:00
d37b191139 [1216] 모듈에 시리즈 추가 - 시리즈가 존재하지 않는 모듈존재 2025-09-12 10:43:13 +09:00
7c9fd0f698 Merge remote-tracking branch 'origin/dev' into feature/ysCha 2025-09-11 09:52:42 +09:00
650e72d91d Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-09-11 09:50:45 +09:00
fb20a1676b 계산기의 마지막 입력숫자가 보이게.. 2025-09-11 09:50:42 +09:00
b651aab959 module 이동 후에도 흡착점 작동하도록 수정 2025-09-11 09:50:25 +09:00
cf969d6f1c Merge remote-tracking branch 'origin/dev' into feature/ysCha 2025-09-10 18:22:46 +09:00
0666612c0a [1216] 모듈에 시리즈 추가 2025-09-10 18:21:30 +09:00
3811f224d6 plan 저장 전 선택한 setupsurface 선택된 경우 초기화 2025-09-10 17:51:07 +09:00
Jaeyoung Lee
fda35b6927 Merge branch 'dev' into feature/design-remake 2025-09-10 14:34:42 +09:00
a183453390 A,B타입 지붕 대응 2025-09-10 14:33:53 +09:00
Jaeyoung Lee
760becfb0d innerLines 오류 수정 2025-09-10 13:45:01 +09:00
Jaeyoung Lee
098fb8efc6 Merge branch 'dev' into feature/design-remake
# Conflicts:
#	src/components/floor-plan/CanvasFrame.jsx
2025-09-10 13:19:06 +09:00
Jaeyoung Lee
473f4f8d74 케라바 지붕 자동 설정 업데이트 2025-09-10 13:16:23 +09:00
c358cba0c5 흡착점 points => getCurrentPoints() 2025-09-09 15:41:10 +09:00
ff5c335c9b 계산기능 추가 2025-09-05 15:34:55 +09:00
9999d1bae0 svg 이미지 해상도 경고 2025-09-04 17:07:45 +09:00
c5700c62cc FlowLine contains an input of type text with both value and defaultValue props. Input elements must be either controlled or uncontrolled 2025-09-04 17:06:53 +09:00
08ae9ac7b9 원상복구 prd-deploy checkout 2025-09-03 19:20:03 +09:00
c4baa119b1 prd-deploy checkout 2025-09-03 19:12:47 +09:00
acffaa5ebd input 계산기 디자인 수정 2025-09-03 14:49:36 +09:00
87d058fd07 skeleton.ts 라이브러리 2025-09-03 10:18:13 +09:00
61a44704fd Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/batch-angle 2025-09-03 09:48:17 +09:00
ee26f28446 skeleton.ts 라이브러리 2025-09-03 09:26:13 +09:00
71a7fab4ea skeleton.ts 라이브러리 2025-09-03 08:58:34 +09:00
114bf94d92 skeleton.ts 라이브러리 2025-09-03 08:23:03 +09:00
6dd66ee27d value, defaultValue 동시 존재 안됨 => defaultValue 삭제 2025-09-02 17:28:37 +09:00
662b38aac7 <thead><th></th></thead> => <tr></tr>추가 2025-09-02 17:26:18 +09:00
8f2a78ef1e big.mjs:139 Uncaught Error: [big.js] Invalid number
at klass.setLength (QLine.js:72:36) (NaN => 0 처리)
2025-09-02 17:25:00 +09:00
872c27734b 동일 경사, 동일 방면 체크되어있을 경우에만 정렬 2025-09-02 14:30:12 +09:00
098147bc6b module 설치 후에도 setTimeout 적용 2025-09-01 14:40:12 +09:00
cee1567438 배치면 기초작업중 2025-09-01 13:52:41 +09:00
f15ff10bf6 input 계산기 수정 2025-08-29 17:10:05 +09:00
38a1907842 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-08-29 15:49:20 +09:00
0c8374b43d input 계산기 추가 및 전각=>반각 2025-08-29 15:48:44 +09:00
bad50e11b1 wall이 있는 roof는 사진에서 제외 2025-08-28 15:27:24 +09:00
3d67e5eec8 wall이 있는 roof인 경우 roofMaterial 세팅 제외 2025-08-28 15:19:59 +09:00
Jaeyoung Lee
20eccab581 Merge branch 'dev' into feature/design-remake 2025-08-27 10:04:42 +09:00
Jaeyoung Lee
e35cacf520 a,b 패턴 지붕 수정
용마루 생성 끝, 지붕면 생성 중
2025-08-27 10:02:53 +09:00
a55fca439c Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-08-26 16:12:39 +09:00
9a536970c4 배치면에서는 대각선 허용 2025-08-26 16:12:03 +09:00
9f1649dc0e Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-08-26 15:27:34 +09:00
06815fc04a [1237]모듈설정, 공법설정시에 미리 규정의 내용이 입력, 기본지붕설정 <=> 개뱔지붕설정[0] 2025-08-26 15:27:09 +09:00
27164dc3a7 QLine길이 계산 수정 및 흐름방향 화살표 위치 수정 2025-08-26 14:02:04 +09:00
21634a7a8a [1237]모듈설정, 공법설정시에 미리 규정의 내용이 입력, 기본지붕설정 <=> 개뱔지붕설정[0] 2025-08-25 17:39:06 +09:00
77510148e3 [1256] : 【HANASYS DESIGN】ラックレス金具の中間部と端部の色分けをしたい
지지금구 색 변경
2025-08-25 14:42:07 +09:00
4375666084 [1252] 일본의 전각 숫자, 반각 함수변경 2025-08-22 18:28:48 +09:00
736328ef0f [1252] 일본의 전각 숫자, 반각 함수변경 2025-08-22 11:18:49 +09:00
848f75bafa [1252] 일본의 전각 숫자, 반각 함수변경 2025-08-22 10:43:16 +09:00
4b8c0510ca [1252] 일본의 전각 숫자, 반각 함수변경 2025-08-22 10:05:07 +09:00
301e6c136c [1252] 일본의 전각 숫자, 반각 숫자 입력 2025-08-21 18:45:02 +09:00
6c89e6ffa6 [1249] 주소의 상세 주소란의 필수 입력을 중지 2025-08-21 09:26:51 +09:00
cef764fd0e [1267] 500에러 - str => Number 변환 (Number로 한번더) 2025-08-20 19:01:44 +09:00
a663faf359 [1267] 500에러 - str => Number 변환 (이전으로 복구) 2025-08-20 18:28:47 +09:00
5cf5ef55af [1267] 500에러 - str => Number 변환 2025-08-20 17:00:17 +09:00
d98eba97a7 pcsMaker는 항상 새로 불러오도록 수정 2025-08-20 14:02:09 +09:00
4a914e0aea roof의 중앙 지점에서 모듈 스크린샷 2025-08-19 18:26:11 +09:00
102cc4d672 줌 상태에 따라 mouseLine 수정 2025-08-19 15:32:33 +09:00
2aca4d22ec 줌 상태에 따라 그리드 복사, 이동 시 잘리는 현상 수정 2025-08-19 10:33:18 +09:00
0f2719a2f0 줌 상태 변경에 따라 그리드 상태 수정, 흡착점 모드 시작 시 ON 2025-08-19 10:17:27 +09:00
Jaeyoung Lee
8c153430cd 용마루, 박공 지붕 추가. 2025-08-13 13:23:29 +09:00
Jaeyoung Lee
7175cd0485 지붕 모양 파악 로직 수정 2025-08-13 13:01:43 +09:00
Jaeyoung Lee
c10a46e86f 불필요 라인 제거 2025-08-13 11:29:54 +09:00
Jaeyoung Lee
d8389c1d9f 지붕 덮개 작도 로직 수정 시작. 2025-08-13 11:27:46 +09:00
bdef681898 type 유지 2025-08-08 15:56:17 +09:00
9db8b949ff Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-08-08 15:05:24 +09:00
6819196d20 보조선 확정시 자르기 제거 2025-08-08 15:05:17 +09:00
d03937cc92 [1237] 모듈설정, 공법설정시에 미리 규정의 내용이 입력되어있는것과 같은 설정 - 선택하세요 삭제 2025-08-08 14:51:07 +09:00
cb42ad82de innerLine size 보존 2025-08-08 10:57:28 +09:00
7a0db7abfb capture 처리 추가 2025-08-07 18:21:54 +09:00
de39eac555 숫자입력 ''=> 0 으로 처리 2025-08-05 13:53:58 +09:00
bab3039d6e 숫자입력 ''=> 0 으로 처리 2025-08-05 11:29:59 +09:00
e285210927 지붕재선택 메뉴의 데이터 동기화 2025-08-05 11:29:06 +09:00
dcd7ace830 [1218] WINDOW를 이동할 때 잡는 영역 - QSelect 제외 2025-08-04 17:27:13 +09:00
959964c876 Merge remote-tracking branch 'origin/dev' into feature/ysCha 2025-08-04 17:09:27 +09:00
f11a7d68e7 [1218] WINDOW를 이동할 때 잡는 영역 - 전체에서 input, button, select, textarea, [contenteditable] 제외 2025-08-04 17:08:47 +09:00
359c7c458f 지붕면 할당 로직 수정 2025-08-04 16:38:24 +09:00
58d35a2881 [1218] WINDOW를 이동할 때 잡는 영역 - 전체로 변경 2025-08-04 15:53:34 +09:00
1b57602cb6 [1217]줌 50->10% 2025-08-04 13:56:15 +09:00
fa9663f123 [1226]브라우저를 다시 로드하면 지붕재가 리셋 2025-08-04 11:37:38 +09:00
856086a271 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-08-01 16:44:16 +09:00
591b24974c 방향 설정 수정 2025-08-01 16:44:10 +09:00
0a258c3807 [1226]브라우저를 다시 로드하면 지붕재가 리셋 2025-08-01 13:28:51 +09:00
cc7f1ad6af globalPitch 추가 2025-07-31 16:04:56 +09:00
984836d523 zoom 0.1로 수정 2025-07-31 14:32:12 +09:00
d15e4ebd53 roof.pitch 확인 제거 2025-07-31 13:23:35 +09:00
95dcc69ab4 Merge remote-tracking branch 'origin/dev' into feature/ysCha 2025-07-31 10:07:42 +09:00
b3254d1e5d [1219] 견적서 엑셀에 방위각도 추가 2025-07-31 10:06:34 +09:00
992d87412c 보조선 겹치는 부분 자르기 추가 2025-07-31 09:52:35 +09:00
6a5ba73274 degree 계산식 수정 2025-07-30 18:01:45 +09:00
31751c26b9 모든 점이 같은 직선상일 경우 제거 2025-07-30 16:37:42 +09:00
f82a355f01 메뉴변경시 팝업 닫기 2025-07-30 16:36:31 +09:00
Jaeyoung Lee
4e14e1d5bf Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-07-30 10:36:33 +09:00
Jaeyoung Lee
5e017ccbb7 동선이동, 형이동 시 좌표 계산 문제 수정 2025-07-30 10:36:25 +09:00
445270bb80 외벽선 반올림 제거 2025-07-30 09:52:39 +09:00
9ec5249122 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-07-29 17:09:40 +09:00
7e3820e6b1 hipandgable 추가 2025-07-29 17:09:33 +09:00
1e678cfd81 Merge remote-tracking branch 'origin/dev' into feature/ysCha 2025-07-29 16:49:33 +09:00
2b956bbc3b [1218] WINDOW를 이동할 때 잡는 영역 2025-07-29 16:49:06 +09:00
37e50c14cc 기준 좌표 수정 2025-07-29 16:44:27 +09:00
718fbc16fa Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-07-29 14:55:30 +09:00
123657431d 좌우로 넓어진 형상은 스케일 조정 x 2025-07-29 14:55:23 +09:00
ce1fab884d [1191] T01에 한정 엑셀에 영업점 주소, 전화번호, fax 2025-07-29 10:04:42 +09:00
Jaeyoung Lee
699bfa9af4 운영 소스 반영 확인을 위한 commit 2025-07-28 14:27:05 +09:00
5469037de9 [1205] DXF => PNG 추가, 메시지 일본어 추가 2025-07-28 14:06:59 +09:00
81fd0168e7 보조선 이동, 복사 시 innerLines에 추가 2025-07-25 16:22:58 +09:00
d1da73849f 개구, 도머 설치시 sizesetting popup open 2025-07-25 14:17:36 +09:00
40970c5580 [] 2025-07-24 15:54:02 +09:00
cdda10c30c 보조선 처리 추가 2025-07-24 13:58:13 +09:00
59d3bd61e2 지붕면 할당 알고리즘 수정 2025-07-24 13:19:09 +09:00
4684b45883 보조선 innerLines에 포함 안되는 현상 수정 2025-07-24 13:14:30 +09:00
e14e43f778 round처리 수정 2025-07-24 12:46:47 +09:00
5e86c98afd 지붕면 할당 로직 수정 2025-07-24 11:30:54 +09:00
Jaeyoung Lee
45a4ac84de Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-07-23 14:59:33 +09:00
Jaeyoung Lee
a64eaae57d 케라바 지붕 8각이상 동선, 형이동 시 문제점 수정 2025-07-23 14:59:25 +09:00
ca0620bd23 내부 확인 로직 수정 2025-07-22 14:03:48 +09:00
d52fdb23a1 오브젝트 배치 오류 수정 2025-07-22 13:53:06 +09:00
b979e6cfa4 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-07-22 09:53:50 +09:00
eb03054804 주석 수정 2025-07-22 09:53:43 +09:00
ef607afcf3 [1196] 자동회로구성시 회로구성후 남는 모듈에대해 얼럿 2025-07-22 09:07:16 +09:00
2d8a7034d8 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-07-22 08:59:19 +09:00
fb9ed9949d [1196] 일시적으로 2차점 숨김처리 2025-07-21 15:36:23 +09:00
4875985685 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-07-21 15:05:15 +09:00
a319df0120 캡쳐 로직 수정 2025-07-21 15:05:08 +09:00
Jaeyoung Lee
8e3a3d7c06 동선이동 관련 오류 수정 2025-07-21 14:24:25 +09:00
0bea975130 지붕면 할당 시 문제 수정 2025-07-21 11:38:17 +09:00
553a9340f4 보조선 작성 완료 시 선택 안되는 현상 수정 2025-07-21 09:42:31 +09:00
eb255d5148 arrow에 따라 축소지점 수정 2025-07-18 18:26:35 +09:00
031643c092 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-07-18 18:24:28 +09:00
edb0783e26 지붕면 할당 수정 2025-07-18 18:24:22 +09:00
Jaeyoung Lee
2f6a4a3ee8 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-07-18 14:05:01 +09:00
Jaeyoung Lee
7a019730f5 동선이동 로직 변경 2025-07-18 14:04:57 +09:00
911ec78055 지붕면 할당 로직 수정 2025-07-17 16:11:42 +09:00
7ca01985b6 Merge pull request 'dev' (#212) from dev into prd-deploy
Reviewed-on: #212
2025-07-16 13:57:57 +09:00
e60989d318 modulePoints 추가 2025-07-16 13:11:30 +09:00
Jaeyoung Lee
44508faf04 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-07-15 15:01:07 +09:00
Jaeyoung Lee
59539e4a60 [1186, 1178] 동이동, 형이동 대응 2025-07-15 15:01:03 +09:00
e9f3cb8e53 Merge pull request 'dev' (#210) from dev into prd-deploy
Reviewed-on: #210
2025-07-15 14:45:26 +09:00
ea34d469bb wall 길이 표시 제거 2025-07-15 11:22:13 +09:00
10d7a6476a 보조선 관련된 흡착점 추가 2025-07-15 10:26:52 +09:00
8226f6cf3d 보조선 흡착 관련 작업 추가 2025-07-15 09:53:53 +09:00
c58ff87a4c 흡착점 추가 2025-07-14 15:32:22 +09:00
af850fc742 Merge pull request '캡쳐 전 제거항목 추가' (#208) from dev into prd-deploy
Reviewed-on: #208
2025-07-14 13:02:14 +09:00
dc6b268b74 캡쳐 전 제거항목 추가 2025-07-14 10:35:38 +09:00
b432c8792c Merge pull request '[1182] 추가 견적 출력 메시지 표시 변경 및 POPUP 메시지 추가 - 사이즈 변경' (#206) from dev into prd-deploy
Reviewed-on: #206
2025-07-11 17:03:57 +09:00
b29b76a158 [1182] 추가 견적 출력 메시지 표시 변경 및 POPUP 메시지 추가 - 사이즈 변경 2025-07-11 17:02:56 +09:00
60f8539d3f Merge pull request 'dev' (#204) from dev into prd-deploy
Reviewed-on: #204
2025-07-11 16:00:56 +09:00
9f7fedab79 [1182] 추가 견적 출력 메시지 표시 변경 및 POPUP 메시지 추가 2025-07-11 15:59:30 +09:00
47a8274fd0 QPolygon 선택영역 원복 2025-07-11 14:34:52 +09:00
929304456a Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-07-11 14:09:57 +09:00
5102fa2ec0 polygon 선택이 잘 안되는 현상 수정중 2025-07-11 14:09:50 +09:00
25bb9ede08 Merge pull request 'dev' (#202) from dev into prd-deploy
Reviewed-on: #202
2025-07-11 13:10:58 +09:00
Jaeyoung Lee
3d4040e06c Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-07-11 11:23:43 +09:00
7e0e4643e2 QPolygon containsPoint 계산 수정 2025-07-11 11:23:33 +09:00
Jaeyoung Lee
ac9d2b593e Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-07-11 11:23:27 +09:00
Jaeyoung Lee
016395ef9b 확인 용 라인 생성 코드 주석 2025-07-11 11:23:24 +09:00
849ba0e0d5 보조선 작성 시 x, 선택안됨 수정 2025-07-11 11:07:37 +09:00
c70ab4e7e9 NaN일 경우 처리 추가 2025-07-11 10:10:55 +09:00
149842547c Merge pull request '보조선 여부에 따라 3개 이상 연속점 처리 수정' (#200) from dev into prd-deploy
Reviewed-on: #200
2025-07-10 16:21:38 +09:00
b2e5055ed1 보조선 여부에 따라 3개 이상 연속점 처리 수정 2025-07-10 14:26:44 +09:00
d35230cb9b Merge pull request 'dev' (#198) from dev into prd-deploy
Reviewed-on: #198
2025-07-10 09:18:47 +09:00
9c05cde0b0 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-07-09 18:14:10 +09:00
079f3a7ff2 보조선 지붕재 할당 시 로직 수정 2025-07-09 18:13:53 +09:00
7472a8f36e Merge pull request 'dev' (#196) from dev into prd-deploy
Reviewed-on: #196
2025-07-09 17:35:53 +09:00
a093d368f2 Merge pull request '줌 확대, 축소 시 오브젝트 선택 안되는 현상 수정' (#194) from dev into prd-deploy
Reviewed-on: #194
2025-07-09 17:27:30 +09:00
30061ab167 Merge remote-tracking branch 'origin/dev' into feature/ysCha 2025-07-09 17:26:13 +09:00
0badbef52c 줌 확대, 축소 시 오브젝트 선택 안되는 현상 수정 2025-07-09 17:25:36 +09:00
6dc2b8a037 줌 확대, 축소 시 오브젝트 선택 안되는 현상 수정 2025-07-09 16:02:55 +09:00
71d92f12b8 Merge pull request 'dev' (#192) from dev into prd-deploy
Reviewed-on: #192
2025-07-08 17:21:03 +09:00
a05a63ebdc 연속된 3point 체크 로직 수정 2025-07-08 15:48:08 +09:00
f79d94c1b7 3개 이상 연속점 체크 로직 수정 2025-07-08 15:41:19 +09:00
d72444199f Merge pull request 'dev' (#190) from dev into prd-deploy
Reviewed-on: #190
2025-07-08 15:25:04 +09:00
fb9e8386fc 문자열 수정 2025-07-08 15:07:30 +09:00
f6f385e597 모듈 그림자 위로 그려지도록 수정 2025-07-08 14:59:53 +09:00
0cb3b13519 [1187] 【HANASYS DESIGN】モジュール移動について
모듈 이동 이상 수정
2025-07-08 14:33:14 +09:00
dc685f8594 Merge pull request 'dev' (#188) from dev into prd-deploy
Reviewed-on: #188
2025-07-08 13:43:59 +09:00
Jaeyoung Lee
b130d844c2 [1177] 지붕면 작성 불량 2025-07-07 17:57:40 +09:00
Jaeyoung Lee
624815ba0d Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-07-07 17:56:57 +09:00
71d774bf88 Merge pull request 'dev' (#186) from dev into prd-deploy
Reviewed-on: #186
2025-07-07 17:48:33 +09:00
8354770a9e 발전 시뮬레이션 각도 계산 수정 2025-07-07 17:36:33 +09:00
7ccdda6405 캡쳐 전 처리 수정 2025-07-07 17:05:59 +09:00
a4d74bb41b 보조선 자르기 작동 이상 수정 2025-07-07 16:15:38 +09:00
6347b99ba9 Merge pull request 'dev' (#184) from dev into prd-deploy
Reviewed-on: #184
2025-07-07 14:52:59 +09:00
2b0df28ab9 스크린샷 찍을 경우 0,0 을 기준으로 50% 축소 2025-07-07 13:42:50 +09:00
16432f94cd [1179] : 【HANASYS DESIGN】補助線について
보조선 선택 로직 수정
2025-07-07 13:19:38 +09:00
4bd21c1662 [1169] : 【HANASYS DESIGN】補助線を作成し、屋根面を割り当てしたときの表記
번역 추가
2025-07-07 11:31:48 +09:00
530ee1dba7 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-07-07 11:11:18 +09:00
9c54c135b7 발전 시뮬레이션 각도 계산 수정 2025-07-07 11:10:56 +09:00
936cc7bc92 Merge pull request '[1165] 도면을 열고 있을 때, 상부 메뉴를 눌렀을 때의 거동 재수정' (#182) from dev into prd-deploy
Reviewed-on: #182
2025-07-07 11:10:35 +09:00
Jaeyoung Lee
2b6f679f75 [1153]외줄 지붕과 맞배 지붕의 조합 2025-07-07 11:09:53 +09:00
1750ba53fd [1165] 도면을 열고 있을 때, 상부 메뉴를 눌렀을 때의 거동 재수정 2025-07-07 11:07:31 +09:00
cf290b0bc8 Merge pull request 'dev' (#180) from dev into prd-deploy
Reviewed-on: #180
2025-07-04 16:25:30 +09:00
3ab5aec767 [1164] 【HANASYS DESIGN】実測値入力で斜め線を引くときの不具合
- 배치면 그리기 직각 이상 수정
2025-07-04 14:58:48 +09:00
7286ddc97b 발전시뮬레이션 각도계산 수정 2025-07-04 14:51:23 +09:00
3a93aed738 Merge pull request '[1142] : 【HANASYS DESIGN】方位選択時の+,-の表記が異なる' (#178) from dev into prd-deploy
Reviewed-on: #178
2025-07-04 14:18:12 +09:00
d0b75f27fb [1142] : 【HANASYS DESIGN】方位選択時の+,-の表記が異なる
모듈/가대 선택 방위의 +, - 표기를 변경
2025-07-04 14:12:53 +09:00
93cda139de Merge pull request 'dev' (#176) from dev into prd-deploy
Reviewed-on: #176
2025-07-04 11:12:06 +09:00
69fa493428 Merge pull request '[1165] 도면을 열고 있을 때, 상부 메뉴를 눌렀을 때의 거동' (#174) from feature/ysCha into dev
Reviewed-on: #174
2025-07-04 11:11:17 +09:00
b46ce583ad [1165] 도면을 열고 있을 때, 상부 메뉴를 눌렀을 때의 거동 2025-07-04 11:08:44 +09:00
178d53ef14 1172 : 【HANASYS DESIGN】影を配置した時のモジュール
그림자 배치 위로 모듈 추가
2025-07-04 10:07:01 +09:00
516b323a1e 모듈 설치 계산 로직 수정 2025-07-03 18:08:29 +09:00
Jaeyoung Lee
6397de0e76 지붕 형상 로직 변경 및 시계방향 외벽선 작도에 대한 대응 2025-07-03 16:43:27 +09:00
5dae6c3738 Merge pull request '[1146] 발전 시뮬레이션에서 견적서로 이동할 수 없습니다.' (#173) from dev into prd-deploy
Reviewed-on: #173
2025-07-03 15:47:22 +09:00
213b020a3d Merge pull request '[1146] 발전 시뮬레이션에서 견적서로 이동할 수 없습니다.' (#171) from feature/ysCha into dev
Reviewed-on: #171
2025-07-03 15:46:25 +09:00
9957dae967 [1146] 발전 시뮬레이션에서 견적서로 이동할 수 없습니다. 2025-07-03 15:44:58 +09:00
e3ae71a161 Merge pull request 'dev' (#170) from dev into prd-deploy
Reviewed-on: #170
2025-07-03 15:14:57 +09:00
c4c773f929 1156 : 【HANASYS DESIGN】伏図入力と実測値入力の2種類で屋根を作成した時、寸法の整合性が取れない
지붕덮개, 배치면에 따라 모듈 크기 바뀌어야함
2025-07-03 10:29:39 +09:00
be881b656f 가대 길이 조건 수정 2025-07-02 17:09:01 +09:00
9036502f2d offset 조건 수정 2025-07-02 17:01:47 +09:00
3a076b216b Merge pull request 'dev' (#168) from dev into prd-deploy
Reviewed-on: #168
2025-07-02 14:42:09 +09:00
f24715b5d4 50% 원복 2025-07-02 11:00:35 +09:00
422a331d90 50% 제거 2025-07-02 10:52:47 +09:00
2a3ae5b0be 50% 제거 2025-07-02 10:51:57 +09:00
cc3bf02b17 capture 이미지 전 처리 추가 및 문구 수정 2025-07-02 10:47:36 +09:00
a8e0eaadbd Merge pull request '[1136]하제비치값 변경 안되는 현상 수정' (#165) from dev into prd-deploy
Reviewed-on: #165
2025-07-01 14:56:33 +09:00
09fd910b8b 하제비치값 변경 안되는 현상 수정 2025-07-01 14:53:07 +09:00
2c63b22507 Merge pull request 'dev' (#163) from dev into prd-deploy
Reviewed-on: #163
2025-06-27 12:41:03 +09:00
f9fb5b7b50 동면 정렬 로직 추가 2025-06-27 10:46:09 +09:00
3dd1053eff 대각이 아닌경우는 기본값 적용 2025-06-27 09:52:29 +09:00
249d254622 Merge pull request 'dev' (#161) from dev into prd-deploy
Reviewed-on: #161
2025-06-26 14:24:16 +09:00
468296e139 Merge pull request '운영 링크 경로 변경 (개발 -> 운영)' (#159) from feature/ysCha into dev
Reviewed-on: #159
2025-06-26 14:23:33 +09:00
3504e09ba2 운영 링크 경로 변경 (개발 -> 운영) 2025-06-26 14:22:21 +09:00
c3e4570f05 Merge pull request '한쪽흐름인 경우 설치면 이상 수정' (#158) from dev into prd-deploy
Reviewed-on: #158
2025-06-26 14:09:06 +09:00
adaa25dd53 한쪽흐름인 경우 설치면 이상 수정 2025-06-26 13:36:03 +09:00
3ac9e56def Merge pull request '[1130] 【HANASYS DESIGN】ラックレス工法で千鳥配置できない' (#156) from dev into prd-deploy
Reviewed-on: #156
2025-06-26 11:08:14 +09:00
66b626b3ea [1130] 【HANASYS DESIGN】ラックレス工法で千鳥配置できない
랙리스 공법인 경우 설치 안되는 현상 수정
2025-06-26 09:52:04 +09:00
61081475f7 Merge pull request 'dev' (#154) from dev into prd-deploy
Reviewed-on: #154
2025-06-26 09:31:56 +09:00
88a22c1ed1 Merge pull request '[1122]DESINGの見積excel様式の追加 - 견적서 추가' (#152) from feature/ysCha into dev
Reviewed-on: #152
2025-06-25 17:36:52 +09:00
1255d30c3a [1122]DESINGの見積excel様式の追加 - 견적서 추가 2025-06-25 17:29:29 +09:00
afa8d5d62a Merge pull request '양단 케이블 count 수정' (#151) from dev into prd-deploy
Reviewed-on: #151
2025-06-25 14:40:13 +09:00
b2f639a461 양단 케이블 count 수정 2025-06-25 13:46:15 +09:00
0d73df5b0e Merge pull request 'prd 팔작지붕 생성시 마루의 길이 판단 수정' (#149) from dev into prd-deploy
Reviewed-on: #149
2025-06-25 13:19:17 +09:00
Jaeyoung Lee
50cf8f6fc8 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-06-24 18:22:11 +09:00
Jaeyoung Lee
8508ce143a 팔작지붕 생성시 마루의 길이 판단 수정 2025-06-24 18:22:01 +09:00
1ce58af019 Merge pull request 'prd logging 설정 logger.log('a', a)' (#147) from dev into prd-deploy
Reviewed-on: #147
2025-06-24 17:52:55 +09:00
dd5580a3a4 Merge pull request 'logging 설정 logger.log('a', a)' (#145) from feature/ysCha into dev
Reviewed-on: #145
2025-06-24 17:51:38 +09:00
9da2a71668 logging 설정 logger.log('a', a) 2025-06-24 17:49:54 +09:00
e659a385bd Merge pull request 'dev' (#144) from dev into prd-deploy
Reviewed-on: #144
2025-06-24 16:55:57 +09:00
05d4fc3fd8 Merge pull request '적설o 피코커 x 와 적설x 피코커 o 서로 바뀜' (#142) from feature/ysCha into dev
Reviewed-on: #142
2025-06-24 16:55:05 +09:00
3cd1d0fec4 적설o 피코커 x 와 적설x 피코커 o 서로 바뀜 2025-06-24 16:54:21 +09:00
a151046943 Merge pull request 'dev' (#141) from dev into prd-deploy
Reviewed-on: #141
2025-06-24 16:07:46 +09:00
Jaeyoung Lee
74c956bf1e 저장데이터 불러올때 배치면이 없는 지붕덮개 있는 경우 메뉴를 지붕덮개로 이동 2025-06-24 14:39:31 +09:00
Jaeyoung Lee
80272b1ffd Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-06-24 14:27:48 +09:00
Jaeyoung Lee
1dfd6bbdc9 지붕 덮개 이동시 오류 사항에 대한 수정 2025-06-24 14:26:57 +09:00
3c1bfbc110 Merge pull request 'dev' (#139) from dev into prd-deploy
Reviewed-on: #139
2025-06-23 17:51:09 +09:00
93e3036aff 1110 : 【HANASYS DESIGN】外周離隔について
모듈 설치영역 offset 수정
2025-06-23 17:12:15 +09:00
Jaeyoung Lee
aa5cc6aea3 지붕덮개 변별로 형상 설정 하는 부분 오류 수정 2025-06-23 16:32:52 +09:00
4596f03d88 Merge pull request 'dev' (#137) from dev into prd-deploy
Reviewed-on: #137
2025-06-20 17:07:42 +09:00
Jaeyoung Lee
3168a29ecd Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-06-20 16:44:25 +09:00
Jaeyoung Lee
4f84221dea 지붕덮개 계산식 수정 2025-06-20 16:44:22 +09:00
c888103fef Merge pull request '랙 없음의 경우 모듈 최대 단수 적용' (#135) from dev into prd-deploy
Reviewed-on: #135
2025-06-20 16:06:08 +09:00
8feefb0b7e 랙 없음의 경우 모듈 최대 단수 적용 2025-06-20 14:22:23 +09:00
16de0d8533 Merge pull request 'debugger 제거' (#133) from dev into prd-deploy
Reviewed-on: #133
2025-06-19 17:32:13 +09:00
36211f04bd debugger 제거 2025-06-19 17:26:48 +09:00
1276230fcc Merge pull request '[1121] 【HANASYS DESIGN】両端ケーブルの積算条件' (#131) from dev into prd-deploy
Reviewed-on: #131
2025-06-19 17:21:49 +09:00
5c592e95cc [1121] 【HANASYS DESIGN】両端ケーブルの積算条件
양단케이블 견적 계산 수정
2025-06-19 17:02:29 +09:00
a41092b896 Merge pull request 'dev' (#129) from dev into prd-deploy
Reviewed-on: #129
2025-06-19 15:42:56 +09:00
a37bf6e4f7 Merge pull request '[1116] 양단케이블 오작동 => M12, S13 추가' (#127) from feature/ysCha into dev
Reviewed-on: #127
2025-06-19 15:41:41 +09:00
c25e8b2a4a [1116] 양단케이블 오작동 => M12, S13 추가 2025-06-19 15:39:23 +09:00
9b8727a63a QPolygon 기본 속성 추가(isMultipleOf45) - polygon이 이루는 모든 각도가 45도의 배수인지 확인하는 컬럼 2025-06-19 15:04:29 +09:00
8853b7ae86 모든 각이 45도의 배수인지 확인하는 함수 추가 2025-06-19 14:56:14 +09:00
9a543c4b90 Merge branch 'dev' into feature/dev-yj 2025-06-19 13:04:27 +09:00
af29c83eb4 [1115] : [見積書作成後の図面の大きさを調整して欲しい]
[작업내용] : 엑셀 다운로드 pdf 다운로드 이미지 비율 수정
2025-06-19 13:04:02 +09:00
6e87357466 원복 2025-06-19 11:22:24 +09:00
0a6d58cba8 보조선의 경우 actualSize planeSize 동일하게 수정 2025-06-19 11:21:53 +09:00
김민식
4056206af7 api: getModuleTypeItemList에 storeId 추가 2025-06-19 10:15:01 +09:00
김민식
bddc753e52 [1118] : [【HANASYS DESIGN】CST053 - DTスリムの強化施工・多雪施工が設置出来ない。]
[작업내용] : 하제비치 수정시 하위 항목 초기화
2025-06-19 10:14:34 +09:00
김민식
c464632b1a [1114] : [【HANASYS DESIGN】HuaweiPCSで併設用のPCSを積算できない。]
[작업내용] : pscItem 비교시 series조건 추가
2025-06-19 10:13:43 +09:00
fd962219dd Merge pull request 'dev' (#126) from dev into prd-deploy
Reviewed-on: #126
2025-06-18 10:43:21 +09:00
e4d6d3ab0d 오른쪽 아래 없는 경우 계산 수정 2025-06-17 15:28:37 +09:00
be399a1b52 [1099] 【HANASYS DESIGN】強化施工・多雪施工で千鳥配置できるモジュールの積算がおかしい。
견적데이터 이상 수정
2025-06-17 15:16:51 +09:00
Jaeyoung Lee
7c25ad0452 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-06-17 10:39:28 +09:00
Jaeyoung Lee
c869d6c44d 지붕덮개 계산식 수정 2025-06-17 10:39:22 +09:00
d867a8dda7 Merge pull request 'dev' (#124) from dev into prd-deploy
Reviewed-on: #124
2025-06-16 17:13:48 +09:00
1c6071b67c Merge pull request '[1104] 견적서에서 Plan 이동이후 모듈가대로 오면 이전Plan으로 되는 오류 + 시뮬레이션에서 모듈가대로 오면 plan 없어지는 오류' (#122) from feature/ysCha into dev
Reviewed-on: #122
2025-06-16 17:12:56 +09:00
fe8723fd8c [1104] 견적서에서 Plan 이동이후 모듈가대로 오면 이전Plan으로 되는 오류 + 시뮬레이션에서 모듈가대로 오면 plan 없어지는 오류 2025-06-16 17:11:53 +09:00
Jaeyoung Lee
f45862568f 지붕덮개 버그 수정 2025-06-16 14:03:50 +09:00
32a9ade07c Merge pull request 'dev' (#121) from dev into prd-deploy
Reviewed-on: #121
2025-06-13 15:32:54 +09:00
Jaeyoung Lee
7d5a235b95 불필요한 로그 및 확인용 포인트 제거 2025-06-13 14:59:28 +09:00
Jaeyoung Lee
0430be1945 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-06-13 14:44:16 +09:00
Jaeyoung Lee
f1d2cfea5b 지붕덮개 처마.케라바 변경 대응 2025-06-13 14:43:58 +09:00
d4f633fb91 Merge pull request 'dev' (#119) from dev into prd-deploy
Reviewed-on: #119
2025-06-12 13:26:39 +09:00
2e6fef951c Merge pull request 'feature/ysCha' (#117) from feature/ysCha into dev
Reviewed-on: #117
2025-06-12 13:10:45 +09:00
2315bb5eca Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-06-12 13:09:00 +09:00
13547f8a2a [1104] 우편번호를 검색 2025-06-12 13:08:31 +09:00
158f8226b0 Merge pull request 'merge 하면서 빠진 부분 수정' (#116) from dev into prd-deploy
Reviewed-on: #116
2025-06-12 10:11:23 +09:00
김민식
aafa5f5948 merge 하면서 빠진 부분 수정 2025-06-11 18:29:54 +09:00
17100beb8c Merge pull request '[1105] : [【HANASYS DESIGN】混合モジュールでの回路構成について]' (#114) from dev into prd-deploy
Reviewed-on: #114
2025-06-11 18:24:57 +09:00
김민식
b74f847f76 [1105] : [【HANASYS DESIGN】混合モジュールでの回路構成について]
[작업내용] : sub module 일 경우 0.66으로 계산하게 수정
2025-06-11 18:18:12 +09:00
8f782e7b17 Merge pull request '설치면에 module이 없는 경우 처리' (#112) from dev into prd-deploy
Reviewed-on: #112
2025-06-11 14:55:25 +09:00
fed817c6d0 설치면에 module이 없는 경우 처리 2025-06-11 11:13:18 +09:00
0005643c94 Merge pull request '사진 찍기 전후 처리 추가' (#110) from dev into prd-deploy
Reviewed-on: #110
2025-06-11 09:31:19 +09:00
87e70d51c1 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-06-10 18:17:50 +09:00
657d971a2b 사진 찍기 전후 처리 추가 2025-06-10 17:02:21 +09:00
c25af0e186 Merge pull request 'dev' (#108) from dev into prd-deploy
Reviewed-on: #108
2025-06-10 14:59:12 +09:00
daf5ec9ce2 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-06-10 14:45:02 +09:00
765cc9c43f 처마커버 최대 단수 적용 2025-06-10 14:04:19 +09:00
08606adae6 처마커버 최대 단수 적용 2025-06-10 13:58:59 +09:00
68980a8aca seq는 1부터 시작 2025-06-10 13:16:41 +09:00
4ca3e4eae6 [1085] : 【HANASYS DESIGN】架台表示の位置について
가대의 경우도 25% 적용할 수 있도록 수정
2025-06-10 11:06:17 +09:00
6d78a45ebf [1099] : 【HANASYS DESIGN】強化施工・多雪施工で千鳥配置できるモジュールの積算がおかしい。
지지금구 설치 이상 수정
2025-06-10 11:01:53 +09:00
a0cca5fbca [1092] : [特定の組み合わせの時、回路割付後のデータローディングが終わらない]
[작업내용] : 가대 생성시 api 호출 부분에 try catch로 로딩바 제거 로직 추가
2025-06-10 10:34:51 +09:00
e19395f612 Merge pull request 'dev' (#106) from dev into prd-deploy
Reviewed-on: #106
2025-06-09 17:10:54 +09:00
b2ff662335 Merge pull request '물건정보에서 저장이후 그림으로 클릭시 에러발생' (#104) from feature/ysCha into dev
Reviewed-on: #104
2025-06-09 17:09:26 +09:00
351df68762 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-06-09 17:08:10 +09:00
6f755b8f07 물건정보에서 저장이후 그림으로 클릭시 에러발생 2025-06-09 17:07:51 +09:00
김민식
a708f28c36 적용버튼 수행 후 팝업 닫는 로직 수정 2025-06-09 11:29:55 +09:00
711df07aa6 회로번호 항상 보이도록 수정 2025-06-05 18:35:30 +09:00
bb7b9c020f 이미지 캡쳐 전처리 후처리 추가 2025-06-05 18:28:58 +09:00
a0c64bdaa8 jimp-test 2025-06-05 17:49:37 +09:00
22deb24fa2 jimp-test 2025-06-05 17:46:10 +09:00
b6a2334fb8 jimp-test 2025-06-05 17:25:45 +09:00
63f12021e4 test
test
2025-06-05 17:17:40 +09:00
9f2fe5f3c8 Merge pull request 'dev' (#103) from dev into prd-deploy
Reviewed-on: #103
2025-06-05 17:05:36 +09:00
5c1c50bd46 resetCommonUtils 위치 수정 2025-06-05 15:22:14 +09:00
c65615465a 팝업 전 처리 추가 2025-06-05 15:16:11 +09:00
faf4c62328 Merge pull request 'dev' (#101) from dev into prd-deploy
Reviewed-on: #101
2025-06-05 11:32:18 +09:00
946aa231a3 견적시 이미지 운영, 개발 분리 2025-06-05 11:28:08 +09:00
c6d2904cfe Update environment configuration files to include S3_PROFILE variable for development and production environments; modify image upload path to incorporate S3_PROFILE. 2025-06-05 11:14:22 +09:00
8d3305cd04 캔버스 이벤트 오류 수정
[작업내용] : 캔버스 화면에서 이벤트 발생 후 캔버스 없는 페이지로 이동시 발생하는 오류 수정
2025-06-05 10:49:44 +09:00
c6e864d4c4 Merge pull request 'dev' (#99) from dev into dev-deploy
Reviewed-on: #99
2025-06-04 19:19:56 +09:00
0923383a42 Merge pull request 'selectedPlan?.planNo?? currentPid or pid' (#98) from feature/ysCha into dev
Reviewed-on: #98
2025-06-04 19:19:13 +09:00
31bf752970 selectedPlan?.planNo?? currentPid or pid 2025-06-04 19:17:53 +09:00
9d91533922 배치면 초기설정 시 서까래 값 이상 현상 수정 2025-06-04 17:31:42 +09:00
32884830c8 배치면 초기설정 시 서까래 값 이상 현상 수정 2025-06-04 17:21:03 +09:00
acdec6d2f2 mouseMove해당 안되는 메뉴 추가 2025-06-04 15:55:35 +09:00
5395dff081 해당 메뉴가 mouseMove 이벤트를 갖고 있는 경우 제외 2025-06-04 15:52:20 +09:00
bcb8a11d5a zoom값에 따라 mouseLine 안그려지는 부분 수정 2025-06-04 15:45:30 +09:00
ab93c5ffe5 실측치 입력 전환 안되는 현상 수정 2025-06-04 11:06:28 +09:00
904b7afd38 #1085 【HANASYS DESIGN】架台表示の位置について
지지금구 위치 수정
2025-06-04 10:35:14 +09:00
190d5cc19c Merge pull request '복사 로직 오류 수정' (#97) from dev into dev-deploy
Reviewed-on: #97
2025-06-04 09:04:38 +09:00
514545cf65 복사 로직 오류 수정
[작업내용] : 복사 로직 오류 수정
2025-06-04 09:03:21 +09:00
713b773dc0 Merge pull request '견적서 복사 로직 수정' (#96) from dev into dev-deploy
Reviewed-on: #96
2025-06-04 08:56:37 +09:00
387a1fd577 견적서 복사 로직 수정
[작업내용] : 이미지 존재 여부 확인 후 복사 진행
2025-06-04 08:54:00 +09:00
72bbf33317 Merge pull request 'dev' (#95) from dev into dev-deploy
Reviewed-on: #95
2025-06-04 08:37:35 +09:00
5412aff4ef Merge branch 'dev' into feature/dev-yj 2025-06-03 10:26:36 +09:00
16c97ae041 플랜 복사시 견적서 이미지 복사 로직 추가 2025-06-03 10:26:08 +09:00
ac00777cf1 Merge pull request 'dev' (#94) from dev into dev-deploy
Reviewed-on: #94
2025-06-02 17:46:01 +09:00
3d52889924 Merge pull request '[1080]플랜번호 미표시' (#93) from feature/ysCha into dev
Reviewed-on: #93
2025-06-02 17:44:47 +09:00
6e811c9cd4 [1080]플랜번호 미표시 2025-06-02 17:43:37 +09:00
639c057e0c 견적서 복사 -> 이미지 복사 로직 추가 2025-06-02 17:40:38 +09:00
84c18c80f0 [1079] : [ID申請画面翻訳修正]
[작업내용] : 아이디 신청 번역 수정
2025-06-02 15:00:33 +09:00
f61e50284c Merge pull request 'dev' (#92) from dev into dev-deploy
Reviewed-on: #92
2025-06-02 14:01:05 +09:00
b85a9a8aa0 Merge branch 'dev' into feature/dev-yj 2025-06-02 13:49:42 +09:00
8dafc239b3 [1076] : [金属縦葺・ハゼ式折板・重ね式折板・瓦棒葺(芯木なし)の屋根とラックレス工法の組み合わせの時]
[작업내용] : 버그 수정
2025-06-02 13:48:53 +09:00
cf462dc4a3 Merge pull request 'dev' (#91) from dev into dev-deploy
Reviewed-on: #91
2025-06-02 11:10:51 +09:00
c37ba65c80 Merge pull request '[1074]HANASYS 설계 로그인 ID 발급 신청 화면의 표시 변경' (#90) from feature/ysCha into dev
Reviewed-on: #90
2025-06-02 11:10:32 +09:00
49c3cae9fa [1074]HANASYS 설계 로그인 ID 발급 신청 화면의 표시 변경 2025-06-02 11:09:43 +09:00
aec7bb8c03 Merge pull request 'dev' (#89) from dev into dev-deploy
Reviewed-on: #89
2025-06-02 10:52:11 +09:00
806c829ada 지붕면 할당 시 alert 제거 2025-06-02 10:42:18 +09:00
286dad7021 [1076] : [金属縦葺・ハゼ式折板・重ね式折板・瓦棒葺(芯木なし)の屋根とラックレス工法の組み合わせの時、働き幅によるモジュール設置制限ができていない。]
[작업내용] : 망둥어 피치 값 변경
2025-06-02 10:16:23 +09:00
bc607d4828 Merge pull request 'dev' (#88) from dev into dev-deploy
Reviewed-on: #88
2025-06-02 09:06:41 +09:00
042e42c2cf Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-05-30 17:56:48 +09:00
8b81998f8c [1068] : [견적서 생성된 플랜복사 --> 복사된 플랜에서 모듈삭제 --> 견적서 진입이 됨.. ]
[작업내용] : 가대까지 완성 -> 삭제 안함, 그 전상태면 무조건 삭제 로직으로 구현
2025-05-30 17:56:39 +09:00
8b5299366f Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-05-30 17:33:08 +09:00
a58604478d Merge pull request 'dev' (#87) from dev into dev-deploy
Reviewed-on: #87
2025-05-30 17:30:40 +09:00
94985feed8 length가 없는경우 따로 계산 추가 2025-05-30 17:29:37 +09:00
김민식
fc5b111c6e Merge remote-tracking branch 'origin/dev' into dev 2025-05-30 17:24:15 +09:00
김민식
401d752ac7 오타 수정 2025-05-30 17:23:57 +09:00
5986fe51ff 대표라인 설정 수정 2025-05-30 17:23:16 +09:00
김민식
65b778d9e5 Merge remote-tracking branch 'origin/qcast-pub' into dev
# Conflicts:
#	src/components/floor-plan/modal/basic/step/Placement.jsx
2025-05-30 17:20:57 +09:00
김민식
66827d4c87 [1069] : [【HANASYS DESIGN】不正な配置について[アラートの表示]]
[작업내용] : 모듈 배치 화면에 안내 문구 추가
2025-05-30 17:19:35 +09:00
김민식
022125759f 🚨chore: Sync Sass 2025-05-30 17:18:22 +09:00
b71fb0992f Merge pull request 'dev' (#86) from dev into dev-deploy
Reviewed-on: #86
2025-05-30 15:48:09 +09:00
48dc3d6328 Merge branch 'dev' into feature/dev-yj 2025-05-30 14:10:50 +09:00
51d892ed29 모듈 흡착 수정, 모듈창 토글 수정 2025-05-30 14:10:31 +09:00
d16c3869d1 path 변경되면 FloorPlan state값 수정 2025-05-30 13:42:48 +09:00
8be2e3e232 Merge branch 'dev' into feature/dev-yj 2025-05-30 13:06:17 +09:00
9302ba216f 견적서 -> 메뉴 이동 후 캔버스 미노출 오류 수정 2025-05-30 13:06:07 +09:00
0ed1ed5623 견적서 사진 잘리는 현상 수정 2025-05-30 11:29:05 +09:00
2bbe4ea29e Update base URL in development configuration from 'dev.hanssys.jp' to 'dev.hanasys.jp' 2025-05-29 19:20:35 +09:00
7641148fce Add development start script to execute yarn command 2025-05-29 19:14:10 +09:00
f323d312d7 Add development start script to execute 'yarn start:dev' 2025-05-29 19:13:12 +09:00
2be0cd341a Add start scripts for cluster1 and cluster2; log run mode in config export 2025-05-29 19:11:32 +09:00
9c0c851dcb Add start script for cluster1 to execute yarn command 2025-05-29 19:05:37 +09:00
37f35c3a13 Add cluster start scripts and remove unused Config import in Footer 2025-05-29 19:04:11 +09:00
42238db254 Import configuration module in Footer component to display current mode 2025-05-29 18:35:44 +09:00
27e923ecd4 Fix build script in package.json to correctly reference the production environment file. 2025-05-29 16:49:17 +09:00
7c610c8487 Fix build script in package.json to correctly reference the production environment file 2025-05-29 16:48:55 +09:00
cccf74e2eb Merge remote-tracking branch 'origin/dev-deploy' into dev-deploy 2025-05-29 16:48:31 +09:00
4de742d1a3 Fix background image path in useRefFiles hook to use AWS S3 URL instead of local file path 2025-05-29 16:48:09 +09:00
308e41ac26 Fix background image path in useRefFiles hook to use AWS S3 URL format. 2025-05-29 16:47:17 +09:00
72cded314b Merge pull request 'dev' (#85) from dev into dev-deploy
Reviewed-on: #85
2025-05-29 16:45:27 +09:00
a17d547252 Merge pull request 'qna selectbox hidden 처리, alert변경' (#84) from feature/ysCha into dev
Reviewed-on: #84
2025-05-29 16:44:28 +09:00
df323ffb38 qna selectbox hidden 처리, alert변경 2025-05-29 16:42:34 +09:00
313be228be [1072] : [伏図入力の"屋根形状の手動設定"について]
[작업내용] : 메뉴 삭제
2025-05-29 16:37:30 +09:00
da69eaa00a Import configuration module in useRefFiles hook for enhanced background image management. 2025-05-29 16:27:09 +09:00
1a8dfa628b Import configuration module in useRefFiles hook for background image management 2025-05-29 16:26:33 +09:00
52cee232d0 Add environment configuration files for local and development modes, including API and AWS settings 2025-05-29 16:23:47 +09:00
bcf0e3d1bf Update ecosystem configuration for production environment and modify package scripts to use env-cmd for environment management 2025-05-29 16:04:14 +09:00
1cd24aeca9 Refactor image upload process: set fixed ContentType for uploaded images and convert Base64 to Blob for file handling in useRefFiles hook. 2025-05-29 15:55:53 +09:00
1fe86746c1 지붕 흐름방향 수정 2025-05-29 15:47:49 +09:00
2c475ddbdd Merge pull request 'Remove unused environment configuration display from Main component.' (#82) from feature/chore-env into dev
Reviewed-on: #82
2025-05-29 15:39:12 +09:00
5e8542efe5 Remove unused environment configuration display from Main component. 2025-05-29 15:38:46 +09:00
63930d0966 Merge pull request 'Update environment configuration files to include NEXT_PUBLIC_RUN_MODE for development, local.dev, local, and production environments. Adjust package.json script to rename build command for local development.' (#81) from feature/chore-env into dev
Reviewed-on: #81
2025-05-29 15:28:23 +09:00
772cccce89 Update environment configuration files to include NEXT_PUBLIC_RUN_MODE for development, local.dev, local, and production environments. Adjust package.json script to rename build command for local development. 2025-05-29 15:27:52 +09:00
81b9d659f6 Merge pull request 'Fix local development script in package.json to reference the correct .env.local.dev file.' (#80) from feature/chore-env into dev
Reviewed-on: #80
2025-05-29 15:14:07 +09:00
3594e96a87 Fix local development script in package.json to reference the correct .env.local.dev file. 2025-05-29 15:13:36 +09:00
272ff4f93f 텍스트 모드시 config 창은 안닫히도록 수정 2025-05-29 15:00:34 +09:00
a47db47489 Merge pull request 'Add local environment configuration for development: create .env.local.dev file and update package.json with new build script for local development.' (#79) from feature/chore-env into dev
Reviewed-on: #79
2025-05-29 14:55:59 +09:00
3034e66c4f Add local environment configuration for development: create .env.local.dev file and update package.json with new build script for local development. 2025-05-29 14:55:37 +09:00
09e5b17fe4 Merge pull request 'Add local development ecosystem configuration for Next.js application.' (#78) from feature/chore-env into dev
Reviewed-on: #78
2025-05-29 14:48:40 +09:00
2ed020bbcc Add local development ecosystem configuration for Next.js application. 2025-05-29 14:48:11 +09:00
112de1f7b4 Merge pull request 'Update startscript to use local development command for consistency with new environment configuration.' (#77) from feature/chore-env into dev
Reviewed-on: #77
2025-05-29 14:41:35 +09:00
c4e5aa1b01 Update startscript to use local development command for consistency with new environment configuration. 2025-05-29 14:41:03 +09:00
bb76dc9ad4 Merge pull request 'Add local development configuration: introduce config.local.dev.js and update config export logic in config.export.js. Enhance package.json with a new local:dev script for environment management.' (#76) from feature/chore-env into dev
Reviewed-on: #76
2025-05-29 14:39:25 +09:00
ada62ae8ec Add local development configuration: introduce config.local.dev.js and update config export logic in config.export.js. Enhance package.json with a new local:dev script for environment management. 2025-05-29 14:38:20 +09:00
febea389c9 Merge pull request 'Refactor environment configuration: update package.json scripts to use env-cmd for different environments and replace process.env.NEXT_PUBLIC_API_HOST_URL with Config().baseUrl in API calls across multiple components.' (#75) from feature/chore-env into dev
Reviewed-on: #75
2025-05-29 13:09:32 +09:00
3dc205cb46 Refactor environment configuration: update package.json scripts to use env-cmd for different environments and replace process.env.NEXT_PUBLIC_API_HOST_URL with Config().baseUrl in API calls across multiple components. 2025-05-29 13:05:47 +09:00
b8bbaba37f Update environment configuration: change API host URL to production and set development port to 5010 2025-05-29 11:17:23 +09:00
86a67420ea Update API host URL in development environment to use HTTPS 2025-05-29 11:14:35 +09:00
00d87f143f Merge pull request 'dev' (#74) from dev into dev-deploy
Reviewed-on: #74
2025-05-29 10:33:36 +09:00
9c35900ce3 Merge pull request 'Update ecosystem configuration files to differentiate production instances with unique names' (#73) from hotfix/eco-system into dev-deploy
Reviewed-on: #73
2025-05-28 18:54:06 +09:00
18c47cca2c Update ecosystem configuration files to differentiate production instances with unique names 2025-05-28 18:53:43 +09:00
52d25341de Merge pull request 'Add ecosystem configuration files for production environments with specified PORTs' (#72) from hotfix/eco-system into dev-deploy
Reviewed-on: #72
2025-05-28 18:48:47 +09:00
60449017d8 Add ecosystem configuration files for production environments with specified PORTs 2025-05-28 18:48:07 +09:00
71d6639ebd Merge pull request 'Remove NODE_ENV from environment configuration in development and production setups' (#71) from hotfix/eco-system into dev-deploy
Reviewed-on: #71
2025-05-28 18:40:24 +09:00
342b5a37f1 Remove NODE_ENV from environment configuration in development and production setups 2025-05-28 18:39:55 +09:00
e94542f15c Merge pull request 'Update environment configuration to specify PORT for development and production environments' (#70) from hotfix/eco-system into dev-deploy
Reviewed-on: #70
2025-05-28 18:24:02 +09:00
636ac163dd Update environment configuration to specify PORT for development and production environments 2025-05-28 18:23:16 +09:00
dae19a026f Merge pull request 's3운영으로변경' (#69) from feature/ysCha into dev
Reviewed-on: #69
2025-05-28 14:13:04 +09:00
acc873afcb s3운영으로변경 2025-05-28 14:11:53 +09:00
15b1cc84fd Merge pull request 'dev' (#68) from dev into dev-deploy
Reviewed-on: #68
2025-05-28 11:41:35 +09:00
4aec4e389f Merge pull request '1. 첨부파일 체크 문구 변경, 2. [995] module.circuit.fix.not.same.roof.error 일어추가, zipYn: N => NO' (#67) from feature/ysCha into dev
Reviewed-on: #67
2025-05-28 11:40:31 +09:00
cd65d80cce 1. 첨부파일 체크 문구 변경, 2. [995] module.circuit.fix.not.same.roof.error 일어추가, zipYn: N => NO 2025-05-28 11:38:46 +09:00
96eb4f25ae [1046] : 【HANASYS DESIGN】グリッド線の機能追加についての要望
임의 그리드 작동 이상 수정
2025-05-28 11:30:40 +09:00
김민식
f8a46c30fa [1070] : [[도면] 신규물건 작성할 때 이전 도면값을 유지하고 있음]
[작업내용] : 도면 이동시 canvas 데이터 초기화
2025-05-28 11:17:23 +09:00
김민식
74e65ec5c6 [995] : [【HANASYS DESIGN】回路組について]
[작업내용] : 다국어 추가
2025-05-28 10:50:47 +09:00
d7b17e491f Merge pull request 'dev' (#66) from dev into dev-deploy
Reviewed-on: #66
2025-05-28 10:43:41 +09:00
김민식
2ee0dc45f0 [1068] : [[오류] 견적서 생성된 플랜복사 --> 복사된 플랜에서 모듈삭제 --> 견적서 진입이 됨..]
[작업내용] : 회로설정 -> 수동 할당 시 회로 및 가대 삭제
2025-05-28 10:37:02 +09:00
4e8cc409d4 #1068 [오류] 견적서 생성된 플랜복사 --> 복사된 플랜에서 모듈삭제 --> 견적서 진입이 됨.. 2025-05-28 10:26:38 +09:00
a83f0d7017 Merge pull request 'dev' (#65) from dev into dev-deploy
Reviewed-on: #65
2025-05-28 09:09:53 +09:00
8ab41c8eda Merge pull request 'https://www.hanasys.jp/ => https://www.hanasys.jp' (#64) from feature/ysCha into dev
Reviewed-on: #64
2025-05-28 09:08:33 +09:00
7fb232d9ae https://www.hanasys.jp/ => https://www.hanasys.jp 2025-05-28 09:07:09 +09:00
3a37d637ed console 제거 2025-05-27 17:48:56 +09:00
c42c36501a Merge pull request 'dev' (#63) from dev into dev-deploy
Reviewed-on: #63
2025-05-27 16:58:29 +09:00
17e88e5d3c Merge pull request '[1004] 1:1 1) 버튼 색 변경 2) 상세내용 위치 변경' (#62) from feature/ysCha into dev
Reviewed-on: #62
2025-05-27 16:55:22 +09:00
5d3a7aa9cc [1004] 1:1 1) 버튼 색 변경 2) 상세내용 위치 변경 2025-05-27 16:52:42 +09:00
7a005bb161 Merge pull request 'Update environment configuration for development and production: change API host URL and update AWS credentials for Japan server.' (#61) from hotfix/convert-key into dev-deploy
Reviewed-on: #61
2025-05-27 16:01:31 +09:00
44b016dc50 Update environment configuration for development and production: change API host URL and update AWS credentials for Japan server. 2025-05-27 16:00:53 +09:00
5429e5474d Merge pull request 'Update converter API URL in development and production environment files' (#60) from hotfix/convert-key into dev-deploy
Reviewed-on: #60
2025-05-27 15:21:48 +09:00
cb14b3bb78 Update converter API URL in development and production environment files 2025-05-27 15:20:30 +09:00
c8823712a4 Merge pull request 'dev' (#59) from dev into dev-deploy
Reviewed-on: #59
2025-05-26 17:42:03 +09:00
d886bb135f Merge branch 'dev' into feature/dev-yj 2025-05-26 17:34:16 +09:00
ed68a93b20 [1059] : [HANASYS DESIGN】文字作成について]
[작업내용] : 텍스트 입력 모드 토글로 종료 모드 기능 추가
2025-05-26 17:33:45 +09:00
d8a7986120 [1046] - 댓글
1. 임의그리드 우클릭 흡착점에 생성
2. 임의그리드 생성 후 mouseLine 안그려지는 현상 수정
2025-05-26 17:30:03 +09:00
a5a1370135 Merge pull request 'dev' (#58) from dev into dev-deploy
Reviewed-on: #58
2025-05-26 16:56:30 +09:00
058a3eabc6 Merge pull request '[1004] 1:1 1) no data 추가 2)이메일 수정가능' (#57) from feature/ysCha into dev
Reviewed-on: #57
2025-05-26 16:55:36 +09:00
5101fab027 [1004] 1:1 1) no data 추가 2)이메일 수정가능 2025-05-26 16:53:43 +09:00
50aa5ca556 [1066] 새로고침 시, 지붕재 미표시 오류 2025-05-26 15:57:27 +09:00
김민식
af627035cd [938] : [【HANASYS DESIGN】 복도 입력 자릿수 상향, 자릿수 하향에 대해서 伏図入力 桁上げ・桁下げについて]
[작업내용] : 다국어 수정 및 퍼블 변경
2025-05-26 14:44:41 +09:00
김민식
4af6e13db4 🚨chore: Sync Sass 2025-05-26 14:44:04 +09:00
41ab66eb10 Merge pull request 'dev' (#56) from dev into dev-deploy
Reviewed-on: #56
2025-05-26 10:26:59 +09:00
dc6989b303 지붕재 목록 Header에서 조회 2025-05-26 09:52:30 +09:00
745665782c - 동선 이동 시 라인 안잡히는 현상 수정 2025-05-23 16:33:42 +09:00
김민식
765855b361 외벽선 작성 팝업 안내 문구 추가 및 관련 다국어 추가 2025-05-23 16:33:42 +09:00
2cbcd19b65 - 동선 이동 시 라인 안잡히는 현상 수정 2025-05-23 15:55:26 +09:00
김민식
c029560c4a 외벽선 작성 팝업 안내 문구 추가 및 관련 다국어 추가 2025-05-23 15:49:33 +09:00
b4733ebfa6 Merge pull request 'chore: convert api key 수정' (#55) from hotfix/convert-key into dev-deploy
Reviewed-on: #55
2025-05-23 15:38:43 +09:00
a4af9dc1ea chore: convert api key 수정 2025-05-23 15:37:50 +09:00
eda80451a1 Merge remote-tracking branch 'origin/dev' into dev 2025-05-23 15:33:19 +09:00
f8621d2d78 fix: update NEXT_PUBLIC_CONVERTER_API_URL in environment files for secure API access 2025-05-23 15:33:13 +09:00
d0eb50313a Merge pull request 'dev' (#54) from dev into dev-deploy
Reviewed-on: #54
2025-05-23 14:51:20 +09:00
d59883b9a6 Merge pull request '[1004] 1:1 문의게시판 변경' (#53) from feature/ysCha into dev
Reviewed-on: #53
2025-05-23 14:50:40 +09:00
6cb91a26c9 [1004] 1:1 문의게시판 변경 2025-05-23 14:49:34 +09:00
03ec99dbb4 Merge pull request 'dev' (#52) from dev into dev-deploy
Reviewed-on: #52
2025-05-23 14:33:52 +09:00
a7f572562e Merge pull request '[1004] 1:1 문의게시판 추가' (#51) from feature/ysCha into dev
Reviewed-on: #51
2025-05-23 14:31:09 +09:00
bbb8bef37d [1004] 1:1 문의게시판 추가 2025-05-23 14:30:05 +09:00
cf3e64deb5 지붕면 할당 시 순서 재 정렬 되는 현상 수정 2025-05-23 13:17:33 +09:00
68d961858f Merge pull request 'dev' (#50) from dev into dev-deploy
Reviewed-on: #50
2025-05-23 10:38:05 +09:00
37b12b5deb 번역 오류 수정
[작업내용] : alert 문구 ? 삭제
2025-05-23 10:24:21 +09:00
8f693478b8 [995] : [回路組について]
[작업내용] : 번역 누락 수정
2025-05-23 10:20:42 +09:00
ec8d767809 Merge pull request 'a,b 타입 버그 수정' (#49) from dev into dev-deploy
Reviewed-on: #49
2025-05-22 13:45:54 +09:00
Jaeyoung Lee
5ab58de70b a,b 타입 버그 수정 2025-05-22 13:31:55 +09:00
c52d372148 Merge pull request '지붕덮개 버그 수정' (#48) from dev into dev-deploy
Reviewed-on: #48
2025-05-21 17:33:32 +09:00
Jaeyoung Lee
6a0144826c 지붕덮개 버그 수정 2025-05-21 16:34:14 +09:00
ec93257720 Merge pull request 'feature/jaeyoung' (#46) from feature/jaeyoung into dev
Reviewed-on: #46
2025-05-21 13:53:44 +09:00
Jaeyoung Lee
966cc0517b Merge branch 'feature/jaeyoung' of https://git.hanasys.jp/qcast3/qcast-front into feature/jaeyoung 2025-05-21 13:43:27 +09:00
Jaeyoung Lee
2a8d373f67 마루지붕, 케라바지붕 그리기 첫번째 정리 작업본 2025-05-21 13:43:01 +09:00
364d79fafd [1054] - 혼합모듈일 경우 지지금구 설치 수정 2025-05-21 11:44:44 +09:00
18b7abdfd5 Merge pull request '#1052 물건 우편번호 검색팝업 적용기능' (#45) from feature/qcast-1052 into dev
Reviewed-on: #45
2025-05-21 10:07:36 +09:00
27ce59316d #1052 물건 우편번호 검색팝업 적용기능 2025-05-21 10:06:52 +09:00
0295cf6c7a 배치면 그리기 - 흡착점 ON/OFF 작동 오류 수정 2025-05-21 10:04:24 +09:00
56ae730efa Merge pull request '#1053 전각/반각 length체크 수정' (#44) from feature/qcast-1053 into dev
Reviewed-on: #44
2025-05-21 09:40:46 +09:00
a0b3f7fbe5 #1053 전각/반각 length체크 수정 2025-05-21 09:39:58 +09:00
16e9ce173a Merge pull request '#1053 물건화면 담당자 자리수' (#43) from feature/qcast-1053 into dev
Reviewed-on: #43
2025-05-21 09:32:41 +09:00
99886ad61e #1053 물건화면 담당자 자리수 2025-05-21 09:29:16 +09:00
ee2417cf6f 배치면 그리기 - 수직 수평모드 작동 수정 2025-05-20 18:37:38 +09:00
4ec191dcb0 1. 외벽선그리기, 배치면그리기 에서 흡착점 동작
2. 외벽선그리기, 배치면그리기 에서 그리드, 흡착점 선택 불가
3. 이동, 복사 시 길이 줄어듬
4. 옵션 설정 닫을 때, 임의그리드,흡착점 선택  해제
2025-05-20 17:02:56 +09:00
김민식
4bbe99bda8 Merge remote-tracking branch 'origin/qcast-pub' into dev 2025-05-20 15:36:44 +09:00
김민식
afd59e580f 그리드 이동, 복사 버그 수정 2025-05-20 15:36:20 +09:00
cec871fb70 그리드 이동 단위 수정 2025-05-20 10:52:22 +09:00
46dc8123df 임의그리드 이벤트 수정 2025-05-20 10:34:24 +09:00
d4ae092ae5 흡착점 작동 관련 수정 2025-05-20 10:23:36 +09:00
21d4f48109 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/jaeyoung 2025-05-19 15:19:20 +09:00
김민식
70a4c85149 [1049] : [【HANASYS DESIGN】文字修正]
[작업내용] : 다국어 수정
2025-05-19 13:57:34 +09:00
김민식
51acb561b8 Merge remote-tracking branch 'origin/qcast-pub' into dev 2025-05-19 13:52:45 +09:00
김민식
e482538dec [995] : [【HANASYS DESIGN】回路組について]
[작업내용] : 수동 회로 할당 시 동일면/경사인 지붕면 일 경우 불가하게 수정 및 관련 다국어 메시지 추가
2025-05-19 13:50:39 +09:00
김민식
ec48350c05 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into qcast-pub
# Conflicts:
#	src/locales/ja.json
2025-05-19 13:48:15 +09:00
김민식
69c48cecf8 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev
# Conflicts:
#	src/locales/ja.json
2025-05-19 10:32:43 +09:00
김민식
46cc507e6d Merge remote-tracking branch 'origin/qcast-pub' into dev 2025-05-19 10:30:47 +09:00
김민식
98f87553e6 [1049] : [【HANASYS DESIGN】文字修正]
[작업내용] : 일본어 번역 수정
2025-05-19 10:30:15 +09:00
97d20321ed [1049] : [文字修正]
[작업내용] : 번역 작업 진행
2025-05-19 10:29:49 +09:00
79e33880ac direction 설정 시 default값 추가 2025-05-16 14:39:11 +09:00
0c67a6b212 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/jaeyoung 2025-05-16 10:37:39 +09:00
46f9385163 enterCheck 전부 추가 2025-05-16 10:37:30 +09:00
59a55e4c88 961 : 외벽선 그리기 및 배치면 그리기에 대해서 外壁線を描く・配置面の描画について
1. "외벽선 그리기", "배치면 그리기"에서 선을 그을때 우클릭으로 "undo" 할 수 있도록 해주세요.
2. 마찬가지로 "외벽선 그리기", "배치면 그리기"로 지붕면을 확정할 때 Enter Key로 지붕면을 확정할 수 있도록 해주시기 바랍니다.
2025-05-16 10:31:23 +09:00
b5b2ac2104 보조선 , 지붕선 만나는 경우 지붕선부터 작업 완료 2025-05-16 10:09:22 +09:00
aa49e4c075 Merge pull request 'feature/ysCha' (#42) from feature/ysCha into dev
Reviewed-on: #42
2025-05-15 16:51:42 +09:00
96e989aa40 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-05-15 16:15:22 +09:00
Jaeyoung Lee
c8d806b220 Merge branch 'feature/jaeyoung' of https://git.hanasys.jp/qcast3/qcast-front into feature/jaeyoung 2025-05-15 16:00:49 +09:00
Jaeyoung Lee
410da6f541 지붕 보조선 작성 수정 중 2025-05-15 16:00:40 +09:00
24d8e14235 Merge pull request 'fix: update API server path in development environment to use secure HTTPS URL' (#39) from hotfix/api-server-path into dev
Reviewed-on: #39
2025-05-15 14:13:36 +09:00
e6da798e43 fix: update API server path in development environment to use secure HTTPS URL 2025-05-15 14:13:06 +09:00
00ed6f250b Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-05-14 15:20:00 +09:00
9609cad8f5 ?? => ? 변경
Reviewed-on: #38
2025-05-14 15:18:50 +09:00
80bf9263fd ?? => ? 변경 2025-05-14 15:16:28 +09:00
457253f0f6 범위로 수정 2025-05-14 14:41:23 +09:00
5c03e631ae Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-05-14 14:08:06 +09:00
5b322d0143 feature/ysCha (#37)
[948] - 견적서 양단케이블 selectbox 추가 케이블 체크 가능. 단어 추가
2025-05-14 14:06:45 +09:00
8a49f7169b [948] - 견적서 양단케이블 selectbox 추가 케이블 체크 가능. 단어 추가 2025-05-14 14:00:29 +09:00
6dcf9b6836 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-05-14 12:58:20 +09:00
4bff451dc4 refactor: enhance useRefFiles hook with additional comments and improve file handling logic for background images 2025-05-14 11:22:50 +09:00
e26673b78a fix: update background image URL in useRefFiles hook to utilize AWS S3 base URL 2025-05-14 11:03:43 +09:00
a8c57b1c53 지붕재 할당후 처리 추가 2025-05-14 10:59:29 +09:00
ad12f9b817 fix: update API host URL in development environment file 2025-05-14 10:59:15 +09:00
5e359bb0ba fix: update API host URL and S3 base URL in environment files; refactor useRefFiles hook to utilize environment variables for API endpoints 2025-05-14 10:54:16 +09:00
e8457da05f 거리 0이 아닌 1 미만으로 범위 수정 2025-05-14 10:50:04 +09:00
55d37af144 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-05-14 10:41:59 +09:00
4b120326db length가 1 미만인 경우 actualSize 작성할 필요 없음 2025-05-14 10:30:08 +09:00
2f5f49c69c canvas 시작시 오류 수정 2025-05-14 09:59:46 +09:00
nalpari
3fefb4b22f feat: update environment variables and API endpoint in useImgLoader hook 2025-05-13 16:33:37 +09:00
c9e0650e6a Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-05-13 16:28:23 +09:00
a631f5ccb6 Merge pull request 'feature/aws-s3-upload' (#36) from feature/aws-s3-upload into dev
Reviewed-on: #36
2025-05-13 16:21:10 +09:00
01dda7e43e Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-05-13 16:12:16 +09:00
ad2aa50d83 feat: 캔버스 s3 저장 테스트 및 확장자 수정 2025-05-13 15:24:57 +09:00
a3b25c3403 Merge branch 'feature/jaeyoung' of https://git.hanasys.jp/qcast3/qcast-front into feature/jaeyoung 2025-05-13 15:22:19 +09:00
19ec9ad874 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/jaeyoung
# Conflicts:
#	src/common/common.js
#	src/components/floor-plan/CanvasFrame.jsx
#	src/hooks/roofcover/useWallLineOffsetSetting.js
#	src/util/qpolygon-utils.js
2025-05-13 15:22:11 +09:00
Jaeyoung Lee
ead3429912 Merge branch 'feature/jaeyoung' of https://git.hanasys.jp/qcast3/qcast-front into feature/jaeyoung 2025-05-13 15:21:42 +09:00
Jaeyoung Lee
3574ed3a9b 지붕형상 분할 대응 중 2025-05-13 15:21:33 +09:00
9d3ddd3ec1 Merge branch 'dev' into feature/aws-s3-upload 2025-05-13 15:17:57 +09:00
1d4100a579 chore: nextjs version update 2025-05-13 15:13:59 +09:00
bca3100a12 commonCode get in hook => page 한번만 호출하도록 2025-05-13 15:11:12 +09:00
001c1cfd95 useEffect내부 async 제거 2025-05-13 14:46:57 +09:00
d06733ed4c 원복 2025-05-13 14:36:11 +09:00
bac3fbef70 addLengthText 추가 2025-05-13 14:34:10 +09:00
7bf548de2b 주석 수정 2025-05-13 14:22:25 +09:00
555671ade7 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-05-13 13:03:59 +09:00
c52d914c6e validCheck 수정 2025-05-13 10:55:58 +09:00
a652d09b8d [1036] : [プランをコピー/移動する時の自動保存について]
[작업내용] : 탭간 이동, 복사시 저장 여부 확인 로직 추가
2025-05-13 10:46:26 +09:00
72c020acea pointOnLine 수정 2025-05-13 09:57:36 +09:00
976396cd78 valid에 해당하지않는 polygon이 2개 이상일 경우 처리 2025-05-12 18:18:31 +09:00
f4ba3058c7 range 계산 수정 2025-05-12 16:04:53 +09:00
8b80277884 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-05-12 16:02:00 +09:00
f48a8b31d3 지붕재 분할 시 addLengthText 제거 2025-05-12 14:09:48 +09:00
4b8287579c Merge branch 'dev' into feature/dev-yj 2025-05-12 13:49:29 +09:00
f03d35ec8d [1037] : [モジュール1枚を削除する時の表示]
[작업내용] : 모듈 선택시 아웃라인 두께 조정
2025-05-12 11:11:09 +09:00
fb297b9a0a Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-05-12 09:04:59 +09:00
553cbd44db 오차 적용 2025-05-09 16:52:04 +09:00
56b8917345 [1014] : [819の追加 レイアウト指定配置の自動配置]
[작업내용] : 멀티모듈일 경우에 북면 모듈이 같이 계산되는 로직 수정
2025-05-09 15:48:08 +09:00
김민식
dbae3380d7 Merge remote-tracking branch 'origin/qcast-pub' into dev 2025-05-09 15:36:55 +09:00
김민식
35708f65e9 지붕재별 가대정보 저장 로직수정 2025-05-09 15:36:28 +09:00
aa85de5280 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-05-09 15:30:47 +09:00
391fe39a2d [1014] : [819の追加 レイアウト指定配置の自動配置]
[작업내용] : 하단방향 오류 수정
2025-05-09 15:04:21 +09:00
김민식
d1af28987b Merge remote-tracking branch 'origin/qcast-pub' into dev 2025-05-09 14:19:47 +09:00
김민식
b8dad62116 [929] : [翻訳変更]
[작업내용] : 다국어 수정 및 validation 로직 추가
2025-05-09 14:19:05 +09:00
d54103943d 0번째일 경우 처리 2025-05-09 13:45:10 +09:00
9b6c6cdfae Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-05-09 09:07:15 +09:00
Jaeyoung Lee
f0c2f0bad2 Merge branch 'feature/jaeyoung' of https://git.hanasys.jp/qcast3/qcast-front into feature/jaeyoung 2025-05-08 18:05:44 +09:00
Jaeyoung Lee
2f261cb0c0 박공지붕 동선이동 대응 정리 (혼합 처리 중) 2025-05-08 18:05:38 +09:00
6949236960 [1014] : [819の追加 レイアウト指定配置の自動配置]
[작업내용] : 북면설치 모듈일 경우, 북면과, 북면이 아닐때 분기 처리
2025-05-08 18:02:41 +09:00
e961648d85 [1014] : [819の追加 レイアウト指定配置の自動配置]
[작업내용] : 멀티모듈일때 북면 모듈 포함일 경우 북면이 아닌 면에 설치되는 오류 수정
2025-05-08 16:55:39 +09:00
ae789672b4 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-05-08 16:03:44 +09:00
cbde82b0ab Merge branch 'dev' into feature/dev-yj-layout 2025-05-08 15:16:41 +09:00
김민식
b2065e7699 [1030] : [【HANASYS DESIGN】文字修正]
[작업내용] : 다국어 수정
2025-05-08 14:48:05 +09:00
dc743cee8f getRoofMaterialList 한번만 호출하도록 수정 2025-05-08 14:21:44 +09:00
bb6a796b9f 지붕재 할당 시 추가 작업 및 보조선 제거 조건 수정 2025-05-08 13:44:31 +09:00
105158e298 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/ysCha 2025-05-08 13:33:35 +09:00
ef75ad0ef8 chore: update environment variables for Japanese server configuration 2025-05-08 13:29:13 +09:00
김민식
c0c3295b04 [1027] : [【HANASYS DESIGN】横葺に使用する金具の離隔について]
[작업내용] : length 변경시 하위 필드 초기화
2025-05-08 13:21:54 +09:00
772f465f9d Merge pull request 'dev' (#35) from dev into dev-deploy
Reviewed-on: #35
2025-05-08 12:37:05 +09:00
cbdbade89f Merge pull request '939 - 배치면 그리기에 흡착점 추가' (#34) from feature/ysCha into dev
Reviewed-on: #34
2025-05-08 10:13:16 +09:00
704fe887f1 939 - 배치면 그리기에 흡착점 추가 2025-05-08 10:09:45 +09:00
14764d295c polygonLines중 intersection 포인트가 있는 경우 처리 추가 2025-05-08 09:59:09 +09:00
47245e438c [1014] : [819の追加 レイアウト指定配置の自動配置]
[작업내용] : 불필요 소스 삭제
2025-05-08 09:48:31 +09:00
0fc7ed857d [공통] : [선택면 최상위 이동]
[작업내용] : canvas 객체 선택시 선택된 객체 최상위 레이어으로 이동
2025-05-07 17:56:36 +09:00
f3b8ee40fa [1014] : [819の追加 レイアウト指定配置の自動配置]
[작업내용] : 문구 수정 및 alert icon 정리
2025-05-07 16:56:28 +09:00
3a66920104 Merge branch 'dev' into feature/dev-yj-layout 2025-05-07 15:19:25 +09:00
d244ba3b97 [1014] : [819の追加 レイアウト指定配置の自動配置]
[작업내용] : 고도화 단수 열수 자동 배치 작업
2025-05-07 15:18:59 +09:00
78258fc9c1 roof 나눌때 point 중복 제거 2025-05-07 15:05:44 +09:00
d6b0e4994a # [1014] : 819の追加 レイアウト指定配置の自動配置
# [작업내용] : http://1.248.227.176:43333/issues/1014
2025-05-07 14:47:08 +09:00
8da2ab0820 오차 범위 수정 2025-05-07 13:31:00 +09:00
김민식
f846644145 모듈 간격 영역 추가 2025-05-07 11:34:22 +09:00
김민식
655f0781a2 🚨chore: Sync Sass 2025-05-07 11:34:03 +09:00
381c639d18 roofPoints valid 체크 추가 2025-05-07 11:21:26 +09:00
92c33c9c11 polygonLines가 없는 경우 처리 2025-05-07 10:46:57 +09:00
c01ed83b40 Merge branch 'dev' into feature/dev-yj-layout 2025-05-02 17:01:22 +09:00
김민식
76dd1aaa43 Merge remote-tracking branch 'origin/qcast-pub' into dev 2025-05-02 16:43:15 +09:00
김민식
9a800d307b - 다설시공 다국어 변경
- trestle에서 참조하는 지붕정보에 참조하는 정보중 raftBaseCd -> raft 로 변경
2025-05-02 16:42:38 +09:00
e3df21f827 http://1.248.227.176:43333/issues/1022 상단 유틸리티 title 추가 2025-05-02 16:16:23 +09:00
f5c098546c Merge branch 'dev' into feature/dev-yj-layout 2025-05-02 15:50:37 +09:00
08722e8b51 모듈 레이아웃 설치 작업중 2025-05-02 15:50:27 +09:00
김민식
7f1ef1ace4 Merge remote-tracking branch 'origin/qcast-pub' into dev 2025-05-02 10:01:59 +09:00
김민식
f52fe72d2a 시공법 버튼명 api 데이터로 수정 2025-05-02 10:01:26 +09:00
d5bba49ca9 console.log 삭제 2025-05-02 09:23:59 +09:00
a7eb2f7598 지붕재 할당 함수 수정 2025-04-30 18:05:25 +09:00
902d13b273 오차 범위 수정 2025-04-30 16:17:03 +09:00
937f2eb3fe x가 전부 같거나, y가 전부 같은경우 제거 2025-04-30 13:26:05 +09:00
adc0c41a67 오류 수정 2025-04-30 13:18:48 +09:00
8412462a8b 라인 위에 있는지 확인 함수 수정
지붕재 할당 시 오류 수정
2025-04-30 13:08:58 +09:00
9f88880ac2 주석 내용 추가 2025-04-29 17:50:36 +09:00
9eda4aa834 제대로 안나눠지는 현상 수정 2025-04-29 17:48:38 +09:00
234e7f1cf7 Merge branch 'dev' into feature/dev-yj-layout 2025-04-29 15:58:16 +09:00
c40ba2586f Merge pull request '886 - 견적서 정가 다운로드 : 0 => "open"' (#33) from feature/ysCha into dev
Reviewed-on: #33
2025-04-29 15:04:53 +09:00
fc8e85b76c 886 - 견적서 정가 다운로드 : 0 => "open" 2025-04-29 15:02:19 +09:00
a45bba3a7b 오차범위 계산 추가 2025-04-29 14:26:21 +09:00
ed27f2ed93 모듈 자동 레이아웃 설치 validate 작업중 2025-04-29 13:09:46 +09:00
69763dd413 Merge branch 'feature/jaeyoung' of https://git.hanasys.jp/qcast3/qcast-front into feature/jaeyoung 2025-04-28 18:08:07 +09:00
5f648632dd 최단거리 roof 나누기 추가 2025-04-28 18:07:54 +09:00
Jaeyoung Lee
95e6f4c0a4 박공지붕 6각 대응 추가 후 정리. 2025-04-28 18:06:55 +09:00
1c7b81c99f 모듈 자동 레이아웃 수정 2025-04-28 14:09:58 +09:00
fe957102d9 모듈 자동 레이아웃 설치 2025-04-28 13:20:35 +09:00
034bfb374e Merge pull request '1019 - ja, ko 변경' (#32) from feature/ysCha into dev
Reviewed-on: #32
2025-04-25 14:47:44 +09:00
71d3a33bf4 1019 - ja, ko 변경 2025-04-25 14:46:00 +09:00
0f079080a1 Merge pull request '1018 - 캔버스팝업상태 복사저장, 조회 : unescapeString() if => while 변경' (#31) from feature/ysCha into dev
Reviewed-on: #31
2025-04-25 14:05:14 +09:00
9bf961441b 1018 - 캔버스팝업상태 복사저장, 조회 : unescapeString() if => while 변경 2025-04-25 14:02:35 +09:00
3b96344943 index 세팅 추가 2025-04-24 13:30:58 +09:00
4be94e41a2 Merge pull request '1006 - ja 추가변경' (#30) from feature/ysCha into dev
Reviewed-on: #30
2025-04-24 11:36:19 +09:00
bd62bf7028 1006 - ja 추가변경 2025-04-24 11:34:59 +09:00
93c8bad986 Merge pull request '1012 - top select box link name 변경' (#29) from feature/ysCha into dev
Reviewed-on: #29
2025-04-24 10:48:54 +09:00
203ddfbdf3 1012 - top select box link name 변경 2025-04-24 10:39:47 +09:00
b11424e70e 육지붕일 경우 값 고정 2025-04-24 10:29:36 +09:00
49d85d437b Merge branch 'dev' into feature/dev-yj 2025-04-24 10:27:42 +09:00
88e2c8ed05 Merge pull request '1003 - calcLineActualSize() degree가 undefined => 0 처리' (#28) from feature/ysCha into dev
Reviewed-on: #28
2025-04-24 09:51:20 +09:00
6b56af6119 1003 - calcLineActualSize() degree가 undefined => 0 처리
showLine 함수의 canvas.getObjects().find()가 undefined => Optional chaining (?.) 처리
2025-04-24 09:45:36 +09:00
287ce5c85a 보조선 이동, 복사 시 나누기 10 적용 2025-04-23 17:09:33 +09:00
737fcf0b30 Merge pull request '986 - 시공사번호 추가, 시공사(userId) 데이터만 조회' (#27) from feature/ysCha into dev
Reviewed-on: #27
2025-04-23 16:16:42 +09:00
f3dfc50f6e 986 - 시공사번호 추가, 시공사(userId) 데이터만 조회 2025-04-23 16:13:07 +09:00
5b74a8ee6b Merge branch 'dev' into feature/dev-yj 2025-04-23 15:45:43 +09:00
김민식
77e34d849d Merge remote-tracking branch 'origin/qcast-pub' into dev 2025-04-23 15:44:32 +09:00
김민식
b6549e06b2 setManagementState 삭제 2025-04-23 15:44:05 +09:00
abe0ddb731 Merge branch 'dev' into feature/dev-yj
# Conflicts:
#	src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx
#	src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx
2025-04-23 15:05:56 +09:00
4a8428f9f7 #1008 보조선 관련 오류 수정 2025-04-23 13:39:32 +09:00
김민식
7ff0178b19 roofConstruction -> roofConstructions 수정 2025-04-23 11:11:53 +09:00
김민식
6d310a6a32 Merge remote-tracking branch 'origin/qcast-pub' into dev 2025-04-23 10:55:30 +09:00
김민식
ebd84967f2 moduleSelectionDataLoad 삭제 및 setData 로직 수정 2025-04-23 10:54:58 +09:00
8de8416160 managementStateLoaded삭제 2025-04-23 10:10:57 +09:00
c6b96bec23 chore: update environment variables to use protocol-relative URLs for host 2025-04-23 10:03:25 +09:00
82c8b25895 Update ecosystem configuration for production deployment 2025-04-23 10:02:13 +09:00
김민식
2b81cc192e Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into qcast-pub 2025-04-22 17:09:41 +09:00
김민식
028fa7f9bf LocalStorage -> API 변경 2025-04-22 17:09:18 +09:00
e0d0eaf017 Merge pull request '991-견적서 pricing 버튼 클릭, confirm 노출' (#26) from feature/ysCha into dev
Reviewed-on: #26
2025-04-22 13:57:12 +09:00
04d49e6aa6 991-견적서 pricing 버튼 클릭, confirm 노출 2025-04-22 13:54:40 +09:00
e938feec2f Merge pull request '1006-오타수정 및 변경' (#25) from feature/ysCha into dev
Reviewed-on: #25
2025-04-22 13:24:00 +09:00
d4a83c4ec5 1006-오타수정 및 변경 2025-04-22 13:21:30 +09:00
3d8e8dfed1 Merge pull request 'feature/ysCha' (#24) from feature/ysCha into dev
Reviewed-on: #24
2025-04-21 13:15:43 +09:00
ac4449f3f3 993 - HANASYS設計 => HANASYS DESIGN 변경및 메인 로고 디자인 변경 2025-04-21 13:13:26 +09:00
159bbf2e6e 993 - HANASYS設計 => HANASYS DESIGN 변경및 메인 로고 디자인 변경 2025-04-21 13:12:43 +09:00
김민식
98d5af6d50 Merge remote-tracking branch 'origin/qcast-pub' into dev 2025-04-21 11:33:47 +09:00
김민식
11a7061e0a roof 변경했을때 setupCover 값 안 넣는 버그 수정 2025-04-21 11:33:08 +09:00
61d8c083b2 Merge pull request '975 - 케이블 선택 버그 수정' (#22) from feature/ysCha into dev
Reviewed-on: #22
2025-04-18 16:41:58 +09:00
479f5d342a 975 - 케이블 선택 버그 수정 2025-04-18 16:40:52 +09:00
da162ac259 모듈,회로구성 => 모듈/가대 설정 메뉴에서 roof 선택 안되도록 수정 2025-04-18 16:03:52 +09:00
김민식
c0b38a00b3 Merge remote-tracking branch 'origin/qcast-pub' into dev 2025-04-18 14:52:55 +09:00
김민식
bee4ee3e74 다국어 개행문자 추가 2025-04-18 14:52:29 +09:00
김민식
0fb3a0bb2a Merge remote-tracking branch 'origin/qcast-pub' into dev 2025-04-18 14:41:33 +09:00
김민식
b12b7aaa3a input type text-> number 수정 2025-04-18 14:41:11 +09:00
김민식
cb3e5dfc47 Merge remote-tracking branch 'origin/qcast-pub' into dev 2025-04-18 14:39:30 +09:00
김민식
77b473c0a1 input type text-> number 수정 2025-04-18 14:39:07 +09:00
김민식
253ddc258a Merge remote-tracking branch 'origin/qcast-pub' into dev 2025-04-18 14:25:14 +09:00
김민식
cb960baa0c Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev 2025-04-18 14:25:08 +09:00
김민식
51a2782d3c plan 추가 로직 삼항연산자 -> if 로 변경 2025-04-18 14:24:46 +09:00
d1ea6229a2 치수표시 selector로 변경 2025-04-18 11:44:58 +09:00
김민식
e717c28758 Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev
# Conflicts:
#	README.md
2025-04-18 10:23:33 +09:00
a08c609caa Merge branch 'dev' into feature/yj-layoutSetup 2025-04-18 09:24:53 +09:00
76938af446 지붕면 할당 시에도 실치수 적용 추가 2025-04-17 16:40:40 +09:00
a3b62db4fa 지붕덮개 메뉴 - 복도치수 적용
그 외 메뉴 - 실치수 적용
2025-04-17 16:35:06 +09:00
김민식
21dcdfb3ef 다국어 수정 2025-04-17 14:22:58 +09:00
김민식
29ccf72c5e 수동으로 방위설정 안되던 버그 수정 2025-04-17 14:13:05 +09:00
김민식
4c219ddce9 validation 방식 수정 2025-04-17 14:12:50 +09:00
김민식
ed721f36d0 Merge branch 'feature/yj-layoutSetup' of https://git.hanasys.jp/qcast3/qcast-front into qcast-pub 2025-04-17 14:12:16 +09:00
김민식
30414568e0 다국어 추가 2025-04-17 14:11:55 +09:00
06812b77af Merge branch 'dev' into feature/yj-layoutSetup 2025-04-16 17:17:50 +09:00
bcc91e7609 반대 2025-04-16 15:53:54 +09:00
bdcacd5542 변별로 설정 후 한쪽흐름과 같이 변 속성을 선택할 경우 처리 추가 2025-04-16 15:39:48 +09:00
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
김민식
2897dc26e3 Orientation에서 nextstep시 compas 매개변수 추가 2025-04-11 16:41:28 +09:00
61f1036a24 변별로 설정 - 한쪽흐름 출폭 적용 추가 2025-04-11 10:58:04 +09:00
김민식
08a29aec26 cvrCheck, snowCheck 수정 2025-04-10 14:23:43 +09:00
김민식
a742fc1f9c Merge remote-tracking branch 'origin/qcast-pub' into feature/yj-layoutSetup
# Conflicts:
#	src/hooks/module/useModuleTrestle.js
2025-04-10 13:55:13 +09:00
김민식
cf900c3e92 lenBase -> length 로 수정 2025-04-10 13:54:05 +09:00
김민식
e2b8bc19b1 lenBase 버그 수정 2025-04-10 13:33:42 +09:00
김민식
14aa4227f4 Merge branch 'feature/yj-layoutSetup' of https://git.hanasys.jp/qcast3/qcast-front into qcast-pub 2025-04-10 13:24:41 +09:00
김민식
af63891df2 지붕재 수정시 모듈.가대 정보 초기화 2025-04-10 13:24:29 +09:00
070e9d933f api 오류 시 처리 추가 2025-04-10 11:36:44 +09:00
e67fc749f4 마우스 휠 줌 이벤트 보정 2025-04-10 10:55:00 +09:00
김민식
adf9febc8c Merge branch 'feature/yj-layoutSetup' of https://git.hanasys.jp/qcast3/qcast-front into feature/yj-layoutSetup 2025-04-10 10:54:29 +09:00
김민식
089ffed7af 키 명 수정 2025-04-10 10:54:21 +09:00
김민식
990de88caf 키 값 수정 2025-04-10 10:51:59 +09:00
d53d7f4220 Merge branch 'dev' into feature/yj-layoutSetup 2025-04-10 10:48:13 +09:00
김민식
134becaa93 중복코드 제거 2025-04-10 10:47:00 +09:00
97f0749f5c Merge pull request '견적서 오픈플래그 텍스트' (#19) from feature/qcast-886 into dev
Reviewed-on: #19
2025-04-10 10:46:57 +09:00
김민식
74b3c6dac7 처마력 커버 설치여부 , 적설방지금구설치 여부 값 추가 2025-04-10 10:46:01 +09:00
82210a99c9 견적서 오픈플래그 텍스트 2025-04-10 10:45:36 +09:00
76693640cf Merge branch 'dev' into feature/yj-layoutSetup 2025-04-09 17:39:13 +09:00
546133c27e 오류 문구 수정 2025-04-09 17:29:37 +09:00
8662a37177 # 974 - 지붕형상 설정 전 팝업 제거 2025-04-09 17:22:55 +09:00
defebb21d7 chore: 내부 개발서버용 실행 스크립트 추가 2025-04-09 17:20:05 +09:00
11cf3b8403 chore: 내부 개발 서버 기동을 위한 실행 파일 추가
- 추후에 제거 예정
2025-04-09 17:18:58 +09:00
c869e9d5a3 육지붕 수정 2025-04-09 16:56:43 +09:00
김민식
e71c655d05 Merge branch 'feature/yj-layoutSetup' of https://git.hanasys.jp/qcast3/qcast-front into feature/yj-layoutSetup 2025-04-09 14:36:18 +09:00
김민식
29e8ca25a5 BasicSetting 하단 버튼 리펙터링 2025-04-09 14:35:59 +09:00
0673567900 Merge branch 'feature/yj-layoutSetup' of https://git.hanasys.jp/qcast3/qcast-front into feature/yj-layoutSetup 2025-04-09 14:13:58 +09:00
c42e244f24 좌표가 음수일때 보정 로직 추가 2025-04-09 14:13:55 +09:00
김민식
e940884312 육지붕일때 moduleSelectionData 에 margin 추가 2025-04-09 13:34:34 +09:00
115544edb4 번역추가 2025-04-09 11:04:10 +09:00
김민식
9e9bd8f9c4 withDraggable spinner 추가 2025-04-09 10:39:57 +09:00
2da7ea0c78 Merge branch 'dev' into feature/yj-layoutSetup 2025-04-08 17:43:46 +09:00
00a201704b trestleDetail 작업 2025-04-08 17:43:19 +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
김민식
597aaa6d42 orientation module validation 추가 2025-04-08 16:14:57 +09:00
김민식
468980abbc roofConstruction 첫번째 trestleDetail null로 들어가는 버그 수정 2025-04-08 15:54:31 +09:00
김민식
91ccfcb20e DB에서 불러온 값 roof에 적용 2025-04-08 15:16:44 +09:00
김민식
c35f3c1adb moduleSelectionData trestleDetail 없는 버그 수정 2025-04-08 14:19:03 +09:00
김민식
468c7fda6f moduleSelectionData 수정 2025-04-08 13:03:23 +09:00
김민식
9976ff59d6 Merge remote-tracking branch 'origin/qcast-pub' into feature/yj-layoutSetup 2025-04-07 18:22:01 +09:00
김민식
f548506179 moduleSelectionData 수정 2025-04-07 18:21:31 +09:00
d6d626f2d7 양단 케이블 파라미터 추가 2025-04-07 14:35:23 +09:00
Jaeyoung Lee
46f46d734a 지붕덮개 작성 관련 작업중 (마루지붕 1차 정리) 2025-04-07 10:05:55 +09:00
c0cbcd18a2 Merge branch 'dev' into feature/yj-layoutSetup 2025-04-04 16:58:07 +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
김민식
f3a0504d0b Merge branch 'feature/yj-layoutSetup' of https://git.hanasys.jp/qcast3/qcast-front into qcast-pub 2025-04-04 13:21:11 +09:00
b8f05a6ff0 fix: promise.all 형태로 변경 2025-04-04 13:15:21 +09:00
0a493884c6 Merge branch 'feature/yj-layoutSetup' of https://git.hanasys.jp/qcast3/qcast-front into feature/yj-layoutSetup 2025-04-04 12:48:58 +09:00
김민식
a316664b20 addRoof, index 데이터 추가 2025-04-04 12:47:50 +09:00
804911af3e 모듈 혼합 수 동적 대응 2025-04-04 12:38:49 +09:00
김민식
59f9377cc9 trestleDetail 호출시 trestle 데이터 추가 2025-04-04 12:28:42 +09:00
김민식
15cb2cf270 roofIndex 추가 2025-04-04 11:32:32 +09:00
김민식
1cad8eaf76 Merge remote-tracking branch 'origin/qcast-pub' into feature/yj-layoutSetup 2025-04-04 10:58:38 +09:00
김민식
9400ee7707 trestle 데이터 버그 수정 2025-04-04 10:58:02 +09:00
22f8cd3fa7 모듈 선택 초기화 수정 2025-04-04 10:18:52 +09:00
e872df680d 모듈 설치면 초기화 기능 2025-04-03 17:09:17 +09:00
김민식
02a0e4a67f Merge remote-tracking branch 'origin/qcast-pub' into feature/yj-layoutSetup 2025-04-03 16:32:13 +09:00
김민식
d524b33f56 모듈 관련 데이터 수정시 roofs 데이터 초기화 버그 수정 2025-04-03 16:31:12 +09:00
e7b50ca642 Merge branch 'feature/yj-layoutSetup' of https://git.hanasys.jp/qcast3/qcast-front into feature/module-grouping
# Conflicts:
#	src/locales/ja.json
2025-04-03 16:30:36 +09:00
3f40725647 3중 멀티모듈 대응 2025-04-03 16:28:58 +09:00
aa2685e558 레이아웃설치 수정 2025-04-03 14:33:54 +09:00
f32e772141 모듈 표 추가 2025-04-03 13:36:46 +09:00
7d76929689 고도화 작업 레이아웃 모듈 설치 기능 2025-04-03 11:23:37 +09:00
김민식
6c6e5845ef 적설량, 풍속 등 수정했을때 설정한 roof 초기화 2025-04-03 10:22:48 +09:00
김민식
001143954e QSelectbox value 없을때 title 부분 수정 2025-04-03 10:22:14 +09:00
265593f6dd 모듈 그룹화 작업중 2025-04-02 17:36:25 +09:00
김민식
ce7c90eb6f Merge branch 'feature/yj-layoutSetup' of https://git.hanasys.jp/qcast3/qcast-front into feature/yj-layoutSetup
# Conflicts:
#	src/components/floor-plan/modal/basic/BasicSetting.jsx
#	src/components/floor-plan/modal/basic/step/Trestle.jsx
2025-04-02 17:04:29 +09:00
2e60e848c8 basicsetting 수정 2025-04-02 17:01:37 +09:00
김민식
51aaf342fa Merge remote-tracking branch 'origin/qcast-pub' into feature/yj-layoutSetup
# Conflicts:
#	src/components/floor-plan/modal/basic/BasicSetting.jsx
2025-04-02 16:57:48 +09:00
김민식
82632b962e Trestle 데이터 추가 2025-04-02 16:55:23 +09:00
김민식
bfc7b3fe32 Revert "Trestle 데이터 추가 수정"
This reverts commit 648c2e208a89c0080e49d9c570ac2b3700374b2a.
2025-04-02 16:54:21 +09:00
김민식
648c2e208a Trestle 데이터 추가 수정 2025-04-02 16:54:04 +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
김민식
ebbf010e6e Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into dev
# Conflicts:
#	README.md
2025-04-02 16:08:42 +09:00
b7cbc3ec47 test: deploy test 2025-04-02 16:00:55 +09:00
8c33611440 Merge branch 'dev' into feature/yj-layoutSetup
# Conflicts:
#	src/components/floor-plan/modal/basic/BasicSetting.jsx
2025-04-02 15:21:15 +09:00
김민식
d8e43b0d81 BasicSetting 버그 수정 2025-04-02 15:19:00 +09:00
cb87d6cd23 Merge branch 'dev' into feature/yj-layoutSetup 2025-04-02 14:51:54 +09:00
김민식
8ba79d6a06 Merge remote-tracking branch 'origin/qcast-pub' into dev 2025-04-02 14:50:45 +09:00
김민식
ba06fde9cc useModuleTrestle 추가 2025-04-02 14:50:20 +09:00
1588b8cda7 Merge branch 'dev' into feature/yj-layoutSetup 2025-04-02 14:48:26 +09:00
김민식
9c1df779e4 Merge remote-tracking branch 'origin/qcast-pub' into dev 2025-04-02 14:47:53 +09:00
김민식
2dbbd3a957 roofAtom 추가 2025-04-02 14:47:21 +09:00
김민식
d165267ec1 🚨chore: Sync Sass 2025-04-02 14:47:09 +09:00
da8e0856cb Merge branch 'dev' into feature/yj-layoutSetup
# Conflicts:
#	src/components/floor-plan/modal/basic/BasicSetting.jsx
#	src/locales/ja.json
2025-04-02 14:46:08 +09:00
김민식
78ce43969a Merge remote-tracking branch 'origin/qcast-pub' into dev 2025-04-02 14:23:23 +09:00
김민식
fe10ecf476 PlacementShapeSetting merge 2025-04-02 14:22:15 +09:00
김민식
b16174ec0d Merge remote-tracking branch 'origin/qcast-pub' into dev
# Conflicts:
#	src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx
#	src/locales/ja.json
2025-04-02 14:08:21 +09:00
김민식
964d9bdcc7 basicsetting 로직 수정 2025-04-02 13:56:03 +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
d6b9634a89 레이아웃 설치 validate 추가 2025-04-01 17:18:38 +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
87ef010ae6 레이아웃 문구 추가 2025-04-01 15:40:20 +09:00
7d37deb048 모듈 설치 페이지 변경 2025-04-01 15:31:12 +09:00
97ca91aa29 Merge branch 'dev' into feature/yj-layoutSetup
# Conflicts:
#	src/hooks/module/useModuleBasicSetting.js
2025-04-01 13:26:08 +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
09e985e9b5 Merge branch 'dev' into feature/yj-layoutSetup
# Conflicts:
#	src/locales/ja.json
#	src/locales/ko.json
2025-03-31 14:57:32 +09:00
d10642d6d5 메뉴에서 안쓰는 내용 제거 2025-03-31 13:50:06 +09:00
826739fb0e 혼합 가능 여부 확인 validate 추가 2025-03-31 10:44:56 +09:00
482b6b5477 수동 설치 혼합일 경우 설치 여부 확인 2025-03-31 10:20:45 +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
2bb1b71a0b 레이아웃 설치 작업 2025-03-28 09:40:19 +09:00
de5901492b 기존 저장된 지붕재 index 수정 2025-03-27 15:17:42 +09:00
6f5b70342d - pcs 모델 추가 버그 수정 2025-03-27 14:41:29 +09:00
b88299b78d Merge branch 'dev' into feature/yj-layoutSetup 2025-03-27 14:11:40 +09:00
63d8ae092c 레이아웃 모듈 설치 validate 추가 2025-03-27 14:11:23 +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
7fcaaece53 Merge branch 'dev' into feature/yj-layoutSetup 2025-03-25 16:08:16 +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
yjnoh
5597f8ad70 지붕라인 색칠하기 2025-03-25 15:48:18 +09:00
65ec3d5153 지붕면 할당 시 오류 수정 2025-03-25 15:12:01 +09:00
yjnoh
a7e9aba26c Merge branch 'dev' into feature/yj-layoutSetup
# Conflicts:
#	src/locales/ja.json
#	src/locales/ko.json
2025-03-25 15:09:40 +09:00
yjnoh
592275c0de 배치면 도형 재배치 및 처마,용마루,케라바 타입 변경 로직 변경 2025-03-25 14:49:07 +09:00
c7de33b8b9 fix: popup spinner 동작 추가 2025-03-25 14:20:28 +09:00
8d645d08dc refactor: API Router 코드 일부 리팩토링
- 패턴에 어긋나는 응답 포맷 수정
- 주석 작성
2025-03-25 13:33:36 +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
553a259c1f chore: 도면 이미지 크롭을 위한 sharp lib 추가 2025-03-25 11:02:08 +09:00
97389f7141 feat: 캔버스 이미지 S3로 저장 기능 추가 2025-03-25 11:01:40 +09:00
11edbe1988 feat: cad파일 컨버터 추가 2025-03-24 19:01:09 +09:00
9a9fa522f9 feat: 구글 맵 컨버트 기능 수정 2025-03-24 17:54:32 +09:00
2492b45a66 feat: 구글 맵 컨버트 기능 추가 2025-03-24 17:08:31 +09:00
yjnoh
c631c6344e 레이아웃 설치 작업중 2025-03-24 17:07:19 +09:00
85d9aca6d3 병렬수 0 이상인 경우에만 추가 2025-03-24 14:50:06 +09:00
yjnoh
0ec917b09f 레이어 팝업 수정 2025-03-24 14:07:14 +09:00
a7b9062154 feat: s3 업로드 기본 기능 추가 2025-03-24 13:58:29 +09:00
Jaeyoung Lee
25240bc3c2 동이동, 형이동 기능 추가. 이에 따른 보조선 작성 수정 중 2025-03-24 13:38:46 +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
yjnoh
dc8f033e9f validation 수정 2025-03-24 10:49:56 +09:00
yjnoh
943fd16e4b 클릭시 배치면 데이터 validation 추가 2025-03-24 10:23:35 +09:00
yjnoh
70c706341e Merge branch 'dev' into feature/yj-layoutSetup 2025-03-24 09:35:53 +09:00
yjnoh
c704207d2f 레이아웃설치 validation 2025-03-24 09:35:21 +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
20cef812ae chore: 브라우저용 환경변수 분리 2025-03-21 16:47:14 +09:00
d8341385a4 chore: 환경변수 추가 2025-03-21 16:44:01 +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
yjnoh
9285caf422 Merge branch 'dev' into feature/yj-layoutSetup
# Conflicts:
#	src/locales/ja.json
2025-03-21 15:55:58 +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
yjnoh
ad14bf091f Merge branch 'dev' into dev-yj-layoutSetup 2025-03-19 17:49:28 +09:00
yjnoh
c34a7fc54d 수동시 치조 여부에 따라 모듈 가운데 흡착 2025-03-19 17:49:17 +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
9bb72bfa3a 그림자 제외 처리 2025-03-19 13:38:35 +09:00
yjnoh
8ea6f43ddb Merge branch 'dev' into dev-yj-layoutSetup
# Conflicts:
#	src/hooks/module/useModuleBasicSetting.js
#	src/locales/ja.json
#	src/locales/ko.json
2025-03-19 13:38:16 +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
b6e70f6eb0 innerLines 초기 셋팅 추가 2025-03-19 09:52:35 +09:00
Jaeyoung Lee
3b41952070 지붕 덮개 계산 오류 수정 2025-03-18 16:23:38 +09:00
84e8af50b8 QSelectbox key 중복 제거 2025-03-18 15:55:11 +09:00
yjnoh
8a5bd9f505 단수배치 작업 2025-03-18 15:44:41 +09:00
Jaeyoung Lee
509c307e56 Merge branch 'dev' into feature/jaeyoung 2025-03-18 14:04:48 +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
Jaeyoung Lee
f4e7896bf8 Merge branch 'dev' into feature/jaeyoung 2025-03-18 10:24:54 +09:00
3432d64a3c 8각 안나눠지는 현상 작업 추가 2025-03-18 10:23:42 +09:00
9be21fc2b2 번역 수정 #914 2025-03-18 09:52:14 +09:00
Jaeyoung Lee
2537240bb0 중복된 마루 제거 2025-03-18 09:36:48 +09:00
yjnoh
c467fc7fa8 흡착점 작업 2025-03-18 09:36:11 +09:00
Jaeyoung Lee
10c2668a67 마루 형상 버그 수정 및 innerLines 수정 2025-03-17 19:05:56 +09:00
yjnoh
63db694efa 단수지정배치 작업중 2025-03-17 18:36:39 +09:00
yoosangwook
f915dab239 chore: ecosystem 추가 2025-03-17 18:25:28 +09:00
Jaeyoung Lee
9f76fa97c5 Merge branch 'dev' into feature/jaeyoung
# Conflicts:
#	src/util/qpolygon-utils.js
2025-03-17 17:39:17 +09:00
Jaeyoung Lee
99a584f8cf 지붕덮개 그리기 로직 수정 중 2025-03-17 17:35:43 +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
yjnoh
15ff2989f4 Merge branch 'dev' into dev-yj-layoutSetup 2025-03-17 15:03:27 +09:00
yjnoh
83b27582f7 레이아웃 작업 2025-03-17 15:01:25 +09:00
yjnoh
9e66b3ef7d 레이아웃 설치 동서남북 작업 2025-03-17 11:33:44 +09:00
yjnoh
ae2171c633 레이아웃 설치 2025-03-14 18:35:13 +09:00
yjnoh
f9766a7d21 Merge branch 'dev' into dev-yj-layoutSetup
# Conflicts:
#	src/components/floor-plan/modal/basic/BasicSetting.jsx
#	src/hooks/module/useModuleBasicSetting.js
2025-03-14 18:22:24 +09:00
yjnoh
83038a7c81 Merge branch 'dev' into dev-yj-layoutSetup 2025-03-12 17:27:25 +09:00
yjnoh
3df466a717 레이아웃 설치 데모 작업 2025-03-12 16:44:41 +09:00
Jaeyoung Lee
b3af3cd0e0 외벽선 편집, 오프셋 기능 적용 및 정리 2025-02-25 13:46:10 +09:00
264 changed files with 29936 additions and 7699 deletions

View File

@ -1,11 +1,36 @@
NEXT_PUBLIC_API_SERVER_PATH="http://1.248.227.176:38080"
NEXT_PUBLIC_RUN_MODE="development"
NEXT_PUBLIC_HOST_URL="http://1.248.227.176:4000"
NEXT_PUBLIC_API_SERVER_PATH="https://dev-api.hanasys.jp"
NEXT_PUBLIC_HOST_URL="//1.248.227.176:4000"
NEXT_PUBLIC_API_HOST_URL="https://dev.hanasys.jp"
SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y="
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_bV5zuYMyyIYFlOb3"
NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_yAS4QDalL9jgQ7vS"
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_yAS4QDalL9jgQ7vS"
NEXT_PUBLIC_CONVERTER_DWG_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_a0FLEK6M2oTpXInK"
NEXT_PUBLIC_CONVERTER_DXF_API_URL="https://v2.convertapi.com/convert/dxf/to/png?Secret=secret_a0FLEK6M2oTpXInK"
NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-stg.q-cells.jp:8120/eos/login/autoLogin"
NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin"
# 테스트용
# AWS_REGION="ap-northeast-2"
# AMPLIFY_BUCKET="interplug"
# AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR"
# AWS_SECRET_ACCESS_KEY="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"
# NEXT_PUBLIC_AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com"
# 실제 일본 서버
AWS_REGION="ap-northeast-1"
AMPLIFY_BUCKET="files.hanasys.jp"
AWS_ACCESS_KEY_ID="AKIA3K4QWLZHFZRJOM2E"
AWS_SECRET_ACCESS_KEY="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"
NEXT_PUBLIC_AWS_S3_BASE_URL="//files.hanasys.jp"
S3_PROFILE="dev"
#logging
NEXT_PUBLIC_ENABLE_LOGGING=true

32
.env.local.dev Normal file
View File

@ -0,0 +1,32 @@
NEXT_PUBLIC_RUN_MODE="local.dev"
NEXT_PUBLIC_API_SERVER_PATH="https://dev-api.hanasys.jp"
NEXT_PUBLIC_HOST_URL="//1.248.227.176:4000"
NEXT_PUBLIC_API_HOST_URL="http://1.248.227.176:5000"
SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y="
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_bV5zuYMyyIYFlOb3"
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_yAS4QDalL9jgQ7vS"
NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_a0FLEK6M2oTpXInK"
NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-stg.q-cells.jp:8120/eos/login/autoLogin"
NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin"
# 테스트용
# AWS_REGION="ap-northeast-2"
# AMPLIFY_BUCKET="interplug"
# AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR"
# AWS_SECRET_ACCESS_KEY="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"
# NEXT_PUBLIC_AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com"
# 실제 일본 서버
AWS_REGION="ap-northeast-1"
AMPLIFY_BUCKET="files.hanasys.jp"
AWS_ACCESS_KEY_ID="AKIA3K4QWLZHFZRJOM2E"
AWS_SECRET_ACCESS_KEY="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"
NEXT_PUBLIC_AWS_S3_BASE_URL="//files.hanasys.jp"
S3_PROFILE="dev"

36
.env.localhost Normal file
View File

@ -0,0 +1,36 @@
NEXT_PUBLIC_RUN_MODE="local"
NEXT_PUBLIC_API_SERVER_PATH="https://dev-api.hanasys.jp"
NEXT_PUBLIC_HOST_URL="//1.248.227.176:4000"
NEXT_PUBLIC_API_HOST_URL="http://localhost:3000"
SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y="
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_bV5zuYMyyIYFlOb3"
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_yAS4QDalL9jgQ7vS"
NEXT_PUBLIC_CONVERTER_DWG_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_a0FLEK6M2oTpXInK"
NEXT_PUBLIC_CONVERTER_DXF_API_URL="https://v2.convertapi.com/convert/dxf/to/png?Secret=secret_a0FLEK6M2oTpXInK"
NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-stg.q-cells.jp:8120/eos/login/autoLogin"
NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin"
# 테스트용
# AWS_REGION="ap-northeast-2"
# AMPLIFY_BUCKET="interplug"
# AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR"
# AWS_SECRET_ACCESS_KEY="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"
# NEXT_PUBLIC_AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com"
# 실제 일본 서버
AWS_REGION="ap-northeast-1"
AMPLIFY_BUCKET="files.hanasys.jp"
AWS_ACCESS_KEY_ID="AKIA3K4QWLZHFZRJOM2E"
AWS_SECRET_ACCESS_KEY="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"
NEXT_PUBLIC_AWS_S3_BASE_URL="//files.hanasys.jp"
S3_PROFILE="dev"
#logging
NEXT_PUBLIC_ENABLE_LOGGING=false

View File

@ -1,13 +1,35 @@
NEXT_PUBLIC_RUN_MODE="production"
NEXT_PUBLIC_API_SERVER_PATH="https://api.hanasys.jp/"
NEXT_PUBLIC_HOST_URL="http://1.248.227.176:4000"
NEXT_PUBLIC_HOST_URL="//1.248.227.176:4000"
NEXT_PUBLIC_API_HOST_URL="https://www.hanasys.jp"
SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y="
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_bV5zuYMyyIYFlOb3"
NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_yAS4QDalL9jgQ7vS"
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_yAS4QDalL9jgQ7vS"
NEXT_PUBLIC_CONVERTER_DWG_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_a0FLEK6M2oTpXInK"
NEXT_PUBLIC_CONVERTER_DXF_API_URL="https://v2.convertapi.com/convert/dxf/to/png?Secret=secret_a0FLEK6M2oTpXInK"
# NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="https://q-order.q-cells.jp/eos/login/autoLogin"
# NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="https://q-musubi.q-cells.jp/qm/login/autoLogin"
NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-stg.q-cells.jp:8120/eos/login/autoLogin"
NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin"
NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="https://q-order.q-cells.jp/eos/login/autoLogin"
NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="https://q-musubi.q-cells.jp/qm/login/autoLogin"
# AWS_REGION="ap-northeast-2"
# AMPLIFY_BUCKET="interplug"
# AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR"
# AWS_SECRET_ACCESS_KEY="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"
# NEXT_PUBLIC_AWS_S3_BASE_URL="//files.hanasys.jp"
# 실제 일본 서버
AWS_REGION="ap-northeast-1"
AMPLIFY_BUCKET="files.hanasys.jp"
AWS_ACCESS_KEY_ID="AKIA3K4QWLZHFZRJOM2E"
AWS_SECRET_ACCESS_KEY="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"
NEXT_PUBLIC_AWS_S3_BASE_URL="//files.hanasys.jp"
S3_PROFILE="prd"
#logging
NEXT_PUBLIC_ENABLE_LOGGING=false

1
.gitignore vendored
View File

@ -43,3 +43,4 @@ yarn.lock
package-lock.json
pnpm-lock.yaml
certificates
.ai

3
.gitmessage.txt Normal file
View File

@ -0,0 +1,3 @@
[일감번호] : [제목]
[작업내용] :

View File

@ -1,6 +1,6 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
### Getting Started
First, run the development server:
@ -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

13
dev.ecosystem.config.js Normal file
View File

@ -0,0 +1,13 @@
module.exports = {
apps: [
{
name: 'qcast-front-development',
script: 'node_modules/next/dist/bin/next',
instances: 1,
exec_mode: 'fork',
env: {
PORT: 5010,
},
},
],
}

View File

@ -0,0 +1,13 @@
module.exports = {
apps: [
{
name: 'qcast-front-local-development',
script: 'node_modules/next/dist/bin/next',
instances: 1,
exec_mode: 'fork',
env: {
PORT: 5000,
},
},
],
}

13
ecosystem.config.js Normal file
View File

@ -0,0 +1,13 @@
module.exports = {
apps: [
{
name: 'qcast-front-production',
script: 'node_modules/next/dist/bin/next',
instances: 2,
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
},
},
],
}

View File

@ -3,27 +3,34 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start -p 5000",
"start:dev": "next start -p 5010",
"dev": "env-cmd -f .env.localhost next dev",
"local:dev": "env-cmd -f .env.local.dev next dev",
"build": "env-cmd -f .env.production next build",
"build:dev": "env-cmd -f .env.development next build",
"build:local.dev": "env-cmd -f .env.local.dev next build",
"start:cluster1": "env-cmd -f .env.production next start -p 5000",
"start:cluster2": "env-cmd -f .env.production next start -p 5001",
"start:dev": "env-cmd -f .env.development next start -p 5010",
"lint": "next lint",
"serve": "node server.js"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.772.0",
"ag-grid-react": "^32.0.2",
"axios": "^1.7.8",
"big.js": "^6.2.2",
"chart.js": "^4.4.6",
"dayjs": "^1.11.13",
"fabric": "^5.3.0",
"env-cmd": "^10.1.0",
"fabric": "^5.5.2",
"framer-motion": "^11.2.13",
"fs": "^0.0.1-security",
"iron-session": "^8.0.2",
"jimp": "^1.6.0",
"js-cookie": "^3.0.5",
"mathjs": "^13.0.2",
"mssql": "^11.0.1",
"next": "14.2.21",
"next": "14.2.28",
"next-international": "^1.2.4",
"react": "^18",
"react-chartjs-2": "^5.2.0",
@ -37,6 +44,7 @@
"react-responsive-modal": "^6.4.2",
"react-select": "^5.8.1",
"recoil": "^0.7.7",
"sharp": "^0.33.5",
"sqlite": "^5.1.1",
"sqlite3": "^5.1.7",
"sweetalert2": "^11.14.1",
@ -47,11 +55,14 @@
},
"devDependencies": {
"@turf/turf": "^7.0.0",
"@types/node": "^24.3.0",
"@types/react": "^19.1.11",
"convertapi": "^1.14.0",
"postcss": "^8",
"prettier": "^3.3.3",
"react-color-palette": "^7.2.2",
"sass": "^1.77.8",
"tailwindcss": "^3.4.1"
"tailwindcss": "^3.4.1",
"typescript": "^5.9.2"
}
}

13
prd1.ecosystem.config.js Normal file
View File

@ -0,0 +1,13 @@
module.exports = {
apps: [
{
name: 'qcast-front-production-1',
script: 'node_modules/next/dist/bin/next',
instances: 1,
exec_mode: 'fork',
env: {
PORT: 5000,
},
},
],
}

13
prd2.ecosystem.config.js Normal file
View File

@ -0,0 +1,13 @@
module.exports = {
apps: [
{
name: 'qcast-front-production-2',
script: 'node_modules/next/dist/bin/next',
instances: 1,
exec_mode: 'fork',
env: {
PORT: 5001,
},
},
],
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="8.25" stroke="#101010" stroke-width="1.5"/>
<path d="M7.94995 16.5C10.0485 14.302 13.9289 14.1986 16.05 16.5M14.2455 9.75C14.2455 10.9926 13.2367 12 11.9923 12C10.7479 12 9.73912 10.9926 9.73912 9.75C9.73912 8.50736 10.7479 7.5 11.9923 7.5C13.2367 7.5 14.2455 8.50736 14.2455 9.75Z" stroke="#101010" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

View File

@ -1,21 +1,13 @@
'use client'
import { createContext, useEffect, useState } from 'react'
import { useLocalStorage } from 'usehooks-ts'
import { createContext, useState } from 'react'
export const GlobalDataContext = createContext(null)
const GlobalDataProvider = ({ children }) => {
const [managementState, setManagementState] = useState(null)
const [managementStateLoaded, setManagementStateLoaded] = useLocalStorage('managementStateLoaded', null)
useEffect(() => {
if (managementState !== null) {
setManagementStateLoaded(managementState)
}
}, [managementState])
return <GlobalDataContext.Provider value={{ managementState, setManagementState, managementStateLoaded }}>{children}</GlobalDataContext.Provider>
return <GlobalDataContext.Provider value={{ managementState, setManagementState }}>{children}</GlobalDataContext.Provider>
}
export default GlobalDataProvider

View File

@ -0,0 +1,70 @@
import { NextResponse } from 'next/server'
import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'
const Bucket = process.env.AMPLIFY_BUCKET
const s3 = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
})
const uploadImage = async (file) => {
console.log('🚀 ~ uploadImage ~ file:', file)
const Body = Buffer.from(await file.arrayBuffer())
const Key = `cads/${file.name}`
const ContentType = 'image/png'
await s3.send(
new PutObjectCommand({
Bucket,
Key,
Body,
ContentType,
}),
)
return {
filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`,
fileName: Key,
}
}
export async function POST(req) {
try {
const formData = await req.formData()
const file = formData.get('file')
const result = await uploadImage(file)
return NextResponse.json(result)
} catch (error) {
console.error(error)
return NextResponse.json({ error: 'Failed to upload image' }, { status: 500 })
}
}
export async function DELETE(req) {
try {
const searchParams = req.nextUrl.searchParams
const Key = `cads/${searchParams.get('fileName')}`
console.log('🚀 ~ DELETE ~ Key:', Key)
if (!Key) {
return NextResponse.json({ error: 'fileName parameter is required' }, { status: 400 })
}
await s3.send(
new DeleteObjectCommand({
Bucket,
Key,
}),
)
return NextResponse.json({ message: '이미지 삭제 성공' }, { status: 200 })
} catch (error) {
console.error('S3 Delete Error:', error)
return NextResponse.json({ error: 'Failed to delete image' }, { status: 500 })
}
}

View File

@ -0,0 +1,203 @@
import { NextResponse } from 'next/server'
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { v4 as uuidv4 } from 'uuid'
import { Jimp } from 'jimp'
const Bucket = process.env.AMPLIFY_BUCKET
const s3 = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
})
const checkArea = (obj) => {
const { width, height, left, top } = obj
if (left < 0 || top < 0 || width > 1600 || height > 1000) {
return false
}
return true
}
const cropImage = async (Key, width, height, left, top) => {
try {
// Get the image from S3
const { Body } = await s3.send(
new GetObjectCommand({
Bucket,
Key,
}),
)
const chunks = []
for await (const chunk of Body) {
chunks.push(chunk)
}
const buffer = Buffer.concat(chunks)
let image = await Jimp.read(buffer)
image.autocrop({ tolerance: 0.0002, leaveBorder: 10 })
const resizedImage = await resizeImage(image).then((result) => {
return result
})
return await resizedImage.getBuffer('image/png')
// Convert stream to buffer
// const chunks = []
// for await (const chunk of Body) {
// chunks.push(chunk)
// }
// const imageBuffer = Buffer.concat(chunks)
// const image = await Jimp.read(Body)
// if (!checkResult) {
// processedImage = await image.toBuffer()
// }
//let processedImage
// if (!checkResult) {
// processedImage = await sharp(imageBuffer).toBuffer()
// } else {
// processedImage = await sharp(imageBuffer)
// .extract({
// width: parseInt(width),
// height: parseInt(height),
// left: parseInt(left),
// top: parseInt(top),
// })
// .png()
// .toBuffer()
// }
// return processedImage
} catch (error) {
console.error('Error processing image:', error)
throw error
}
}
//크롭된 이미지를 배경 크기에 맞게 리사이즈
const resizeImage = async (image) => {
//엑셀 템플릿 너비 35.4cm, 높이 12.89cm
const convertStandardWidth = Math.round((35.4 * 96) / 2.54)
const convertStandardHeight = Math.round((12.89 * 96) / 2.54)
// 이미지를 배경의 98%까지 확대 (훨씬 더 크게)
const targetImageWidth = convertStandardWidth * 0.98
const targetImageHeight = convertStandardHeight * 0.98
const scaleX = targetImageWidth / image.bitmap.width
const scaleY = targetImageHeight / image.bitmap.height
let scale = Math.min(scaleX, scaleY) // 비율 유지하면서 최대한 크게
// scale 저장 (나중에 전체 확대에 사용)
const originalScale = scale
let finalWidth = Math.round(image.bitmap.width * scale)
let finalHeight = Math.round(image.bitmap.height * scale)
if (scale >= 0.6) {
// 실제 리사이즈 실행
image.resize({ w: finalWidth, h: finalHeight })
}
//배경 이미지를 생성
const mixedImage = new Jimp({ width: convertStandardWidth, height: convertStandardHeight, color: 0xffffffff })
//이미지를 중앙에 배치
const x = Math.floor((mixedImage.bitmap.width - image.bitmap.width) / 2)
const y = Math.floor((mixedImage.bitmap.height - image.bitmap.height) / 2)
//이미지를 배경 이미지에 합성
mixedImage.composite(image, x, y, {
opacitySource: 1, // 원본 투명도 유지
opacityDest: 1,
})
// scale이 0.8 이하인 경우 완성된 이미지를 전체적으로 확대
if (originalScale <= 0.8) {
const enlargeRatio = 1.5 // 50% 확대
const newWidth = Math.round(mixedImage.bitmap.width * enlargeRatio)
const newHeight = Math.round(mixedImage.bitmap.height * enlargeRatio)
mixedImage.resize({ w: newWidth, h: newHeight })
}
return mixedImage
}
export async function POST(req) {
try {
const formData = await req.formData()
const file = formData.get('file')
const objectNo = formData.get('objectNo')
const planNo = formData.get('planNo')
const type = formData.get('type')
const width = formData.get('width')
const height = formData.get('height')
const left = formData.get('left')
const top = formData.get('top')
const OriginalKey = `Drawing/${uuidv4()}`
/**
* 원본 이미지를 우선 저장한다.
* 이미지 이름이 겹지는 현상을 방지하기 위해 uuid 사용한다.
*/
await s3.send(
new PutObjectCommand({
Bucket,
Key: OriginalKey,
Body: Buffer.from(await file.arrayBuffer()),
ContentType: 'image/png',
}),
)
/**
* 저장된 원본 이미지를 기준으로 크롭여부를 결정하여 크롭 이미지를 저장한다.
*/
const bufferImage = await cropImage(OriginalKey, width, height, left, top)
/**
* 크롭 이미지 이름을 결정한다.
*/
const Key = `Drawing/${process.env.S3_PROFILE}/${objectNo}_${planNo}_${type}.png`
/**
* 크롭이 완료된 이미지를 업로드한다.
*/
await s3.send(
new PutObjectCommand({
Bucket,
Key,
Body: bufferImage,
ContentType: 'image/png',
}),
)
/**
* 크롭이미지 저장이 완료되면 원본 이미지를 삭제한다.
*/
await s3.send(
new DeleteObjectCommand({
Bucket,
Key: OriginalKey,
}),
)
const result = {
filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`,
fileName: Key,
}
return NextResponse.json(result)
} catch (error) {
console.error('Error in POST:', error)
return NextResponse.json({ error: 'Failed to process and upload image' }, { status: 500 })
}
}

View File

@ -0,0 +1,59 @@
import { NextResponse } from 'next/server'
import { S3Client, CopyObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3'
import sharp from 'sharp'
import { v4 as uuidv4 } from 'uuid'
const Bucket = process.env.AMPLIFY_BUCKET
const s3 = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
})
export async function POST(req) {
const { objectNo, planNo, newObjectNo, newPlanNo } = await req.json()
const responseArray = []
//견적서1 번 이미지
const isExistImage1 = await s3.send(
new GetObjectCommand({
Bucket,
Key: `Drawing/${process.env.S3_PROFILE}/${objectNo}_${planNo}_1.png`,
}),
)
//견적서2 번 이미지
const isExistImage2 = await s3.send(
new GetObjectCommand({
Bucket,
Key: `Drawing/${process.env.S3_PROFILE}/${objectNo}_${planNo}_2.png`,
}),
)
//견적서1,2 번 이미지 둘다 있어야함
if (isExistImage1.$metadata.httpStatusCode === 200 && isExistImage2.$metadata.httpStatusCode === 200) {
//견적서1 번 이미지 복사
const copyCommand = new CopyObjectCommand({
Bucket,
CopySource: encodeURI(`${Bucket}/Drawing/${process.env.S3_PROFILE}/${objectNo}_${planNo}_1.png`),
Key: `Drawing/${process.env.S3_PROFILE}/${newObjectNo}_${newPlanNo}_1.png`,
})
const response = await s3.send(copyCommand)
const copyCommand2 = new CopyObjectCommand({
Bucket,
CopySource: encodeURI(`${Bucket}/Drawing/${process.env.S3_PROFILE}/${objectNo}_${planNo}_2.png`),
Key: `Drawing/${process.env.S3_PROFILE}/${newObjectNo}_${newPlanNo}_2.png`,
})
const response2 = await s3.send(copyCommand2)
responseArray.push(response, response2)
return NextResponse.json({ message: '견적서 이미지 복사 성공', responseArray }, { status: 200 })
} else {
return NextResponse.json({ message: '견적서 이미지 복사 실패(존재하지 않는 이미지)', responseArray }, { status: 400 })
}
}

View File

@ -0,0 +1,78 @@
import { NextResponse } from 'next/server'
import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'
const Bucket = process.env.AMPLIFY_BUCKET
const s3 = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
})
export async function GET(req) {
try {
const searchParams = req.nextUrl.searchParams
const q = searchParams.get('q')
const fileNm = searchParams.get('fileNm')
const zoom = searchParams.get('zoom')
/** 구글 맵을 이미지로 변경하기 위한 API */
const API_KEY = 'AIzaSyDO7nVR1N_D2tKy60hgGFavpLaXkHpiHpc'
const targetUrl = `https://maps.googleapis.com/maps/api/staticmap?center=${q}&zoom=${zoom}&maptype=satellite&size=640x640&scale=1&key=${API_KEY}`
const decodeUrl = decodeURIComponent(targetUrl)
/** 구글 맵을 이미지로 변경하기 위한 API 호출 */
const response = await fetch(decodeUrl)
const data = await response.arrayBuffer()
// const buffer = Buffer.from(data)
/** 변경된 이미지를 S3에 업로드 */
const Body = Buffer.from(data)
const Key = `maps/${fileNm}`
const ContentType = 'image/png'
await s3.send(
new PutObjectCommand({
Bucket,
Key,
Body,
ContentType,
}),
)
const result = {
filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`,
fileName: Key,
}
return NextResponse.json(result)
} catch (error) {
console.error(error)
return NextResponse.json({ error: 'Failed to upload image' }, { status: 500 })
}
}
export async function DELETE(req) {
try {
const searchParams = req.nextUrl.searchParams
const Key = `maps/${searchParams.get('fileName')}`
console.log('🚀 ~ DELETE ~ Key:', Key)
if (!Key) {
return NextResponse.json({ error: 'fileName parameter is required' }, { status: 400 })
}
await s3.send(
new DeleteObjectCommand({
Bucket,
Key,
}),
)
return NextResponse.json({ message: '이미지 삭제 성공' }, { status: 200 })
} catch (error) {
console.error('S3 Delete Error:', error)
return NextResponse.json({ error: 'Failed to delete image' }, { status: 500 })
}
}

View File

@ -0,0 +1,72 @@
import { NextResponse } from 'next/server'
import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'
const Bucket = process.env.AMPLIFY_BUCKET
const s3 = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
})
const uploadImage = async (file) => {
const Body = Buffer.from(await file.arrayBuffer())
const Key = `upload/${file.name}`
const ContentType = file.ContentType
await s3.send(
new PutObjectCommand({
Bucket,
Key,
Body,
ContentType,
}),
)
return {
filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`,
fileName: Key,
}
}
export async function POST(req) {
try {
const formData = await req.formData()
const file = formData.get('file')
const result = await uploadImage(file)
result.message = '이미지 업로드 성공'
return NextResponse.json(result)
} catch (error) {
console.error(error)
return NextResponse.json({ error: 'Failed to upload image' }, { status: 500 })
}
}
export async function DELETE(req) {
try {
const searchParams = req.nextUrl.searchParams
const fileName = searchParams.get('fileName')
if (!fileName) {
return NextResponse.json({ error: 'fileName parameter is required' }, { status: 400 })
}
const Key = `upload/${fileName}`
console.log('🚀 ~ DELETE ~ Key:', Key)
await s3.send(
new DeleteObjectCommand({
Bucket,
Key,
}),
)
return NextResponse.json({ message: '이미지 삭제 성공' }, { status: 200 })
} catch (error) {
console.error('S3 Delete Error:', error)
return NextResponse.json({ error: 'Failed to delete image' }, { status: 500 })
}
}

View File

@ -0,0 +1,9 @@
import Qna from '@/components/community/Qna'
export default async function CommunityQnaPage() {
return (
<>
<Qna />
</>
)
}

View File

@ -21,6 +21,8 @@ const defaultEstimateData = {
fileList: [],
fileFlg: '0', //후일 자료 제출 (체크 1 노체크 0)
priceCd: '',
pricingFlag: false, // 가격 처리 플래그 추가
}
/**
@ -45,8 +47,19 @@ const FloorPlanProvider = ({ children }) => {
// const pathname = usePathname()
// const setCorrentObjectNo = useSetRecoilState(correntObjectNoState)
const searchParams = useSearchParams()
const path = usePathname()
const objectNo = searchParams.get('objectNo')
const pid = searchParams.get('pid')
useEffect(() => {
setFloorPlanState((prev) => {
return {
...prev,
objectNo,
pid,
}
})
}, [path])
// useEffect(() => {
// console.log('🚀 ~ useEffect ~ objectNo:')
// if (pathname === '/floor-plan') {

View File

@ -21,8 +21,8 @@ import GlobalLoadingProvider from './GlobalLoadingProvider'
* 서버 컴포넌트에 한해서 개별로 설정할 있음
*/
export const metadata = {
title: 'HANASYS設計',
description: 'HANASYS設計',
title: 'HANASYS DESIGN',
description: 'HANASYS DESIGN',
}
/**
@ -58,6 +58,8 @@ export default async function RootLayout({ children }) {
pwdInitYn: session.pwdInitYn,
custCd: session.custCd,
isLoggedIn: session.isLoggedIn,
builderNo: session.builderNo,
custNm: session.custNm
}
}
if (!headerPathname.includes('/login') && !session.isLoggedIn) {

View File

@ -13,6 +13,7 @@ export const MENU = {
MOVEMENT_SHAPE_UPDOWN: 'movementShapeUpdown', // 동선이동.형올림내림
OUTLINE_EDIT_OFFSET: 'outlineEditOffset', // 외벽선 편집 및 오프셋
ROOF_SHAPE_ALLOC: 'rootShapeAlloc', // 지붕면 항당
ALL_REMOVE: 'allRemove', // 전체 삭제
DEFAULT: 'roofCoveringDefault', // 아무것도 선택 안할 경우
}, // 지붕덮개
BATCH_CANVAS: {
@ -60,6 +61,7 @@ export const LINE_TYPE = {
*/
DEFAULT: 'default',
EAVES: 'eaves',
EAVE_HELP_LINE: 'eaveHelpLine',
GABLE: 'gable',
GABLE_LEFT: 'gableLeft', //케라바 왼쪽
GABLE_RIGHT: 'gableRight', //케라바 오른쪽
@ -125,6 +127,11 @@ export const TRESTLE_MATERIAL = {
BRACKET: 'bracket',
}
export const MODULE_SETUP_TYPE = {
LAYOUT: 'layout',
AUTO: 'auto',
}
export const SAVE_KEY = [
'selectable',
'name',
@ -203,6 +210,21 @@ export const SAVE_KEY = [
'fontWeight',
'dormerAttributes',
'toFixed',
'startPoint',
'endPoint',
'editable',
'isSortedPoints',
'isMultipleOf45',
'from',
'originColor',
'originWidth',
'originHeight',
'skeletonLines',
'skeleton',
'viewportTransform',
'outerLineFix',
'adjustRoofLines',
'northModuleYn',
]
export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype, fabric.Group.prototype]

View File

@ -14,11 +14,13 @@ import { sessionStore } from '@/store/commonAtom'
import { isObjectNotEmpty } from '@/util/common-utils'
import BoardDetailModal from './community/modal/BoardDetailModal'
import Config from '@/config/config.export'
export default function MainPage() {
const [sessionState, setSessionState] = useRecoilState(sessionStore)
const [chagePasswordPopOpen, setChagePasswordPopOpen] = useState(false)
const [changePasswordPopOpen, setChangePasswordPopOpen] = useState(false)
//
const [isSessionLoaded, setIsSessionLoaded] = useState(false)
const router = useRouter()
const { getMessage } = useMessage()
@ -51,6 +53,14 @@ export default function MainPage() {
}
}
useEffect(() => {
if (isObjectNotEmpty(sessionState)) {
if (sessionState?.pwdInitYn !== 'Y') {
setChangePasswordPopOpen(true)
}
}
}, [sessionState])
//
const handleOnChangeRadio = (e) => {
setSearchRadioType(e.target.value)
@ -76,7 +86,7 @@ export default function MainPage() {
useEffect(() => {
if (isObjectNotEmpty(sessionState)) {
if (sessionState?.pwdInitYn !== 'Y') {
setChagePasswordPopOpen(true)
setChangePasswordPopOpen(true)
}
}
}, [sessionState])
@ -85,10 +95,25 @@ export default function MainPage() {
const [open, setOpen] = useState(false)
const [modalNoticeNo, setModalNoticeNo] = useState('')
useEffect(() => {
if (isObjectNotEmpty(sessionState)) {
if (sessionState?.pwdInitYn !== 'Y') {
setChangePasswordPopOpen(true)
} else {
// pwdInitYn 'Y' (false)
setChangePasswordPopOpen(false)
}
}
}, [sessionState])
//if (!isSessionLoaded) return null
return (
<>
{open && <BoardDetailModal noticeNo={modalNoticeNo} setOpen={setOpen} />}
{(!chagePasswordPopOpen && (
{changePasswordPopOpen ? (
<ChangePasswordPop setChangePasswordPopOpen={setChangePasswordPopOpen} />
) : (
<>
<div className="background-bord"></div>
<div className="main-contents">
@ -130,11 +155,8 @@ export default function MainPage() {
<MainContents setFaqOpen={setOpen} setFaqModalNoticeNo={setModalNoticeNo} />
</div>
</>
)) || (
<>
<ChangePasswordPop setChagePasswordPopOpen={setChagePasswordPopOpen} />
</>
)}
</>
)
}

View File

@ -2,15 +2,51 @@
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 />}
{autoLoginParam !== 'Y' ? (
<>
<div className="login-input-frame">
<div className="login-frame-tit ">
<span>{getMessage('site.name')}</span>
@ -23,5 +59,54 @@ export default function AutoLoginPage() {
</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,8 +25,10 @@ export default function Login() {
useEffect(() => {
if (autoLoginParam) {
if (autoLoginParam !== 'Y') {
autoLoginProcess(autoLoginParam)
}
}
// console.log('🚀 ~ checkSession ~ checkSession():', checkSession())
// checkSession().then((res) => {
@ -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
// e.preventDefault() // contextmenu
if (currentObject) {
const isArray = currentObject.hasOwnProperty('arrayData')
if (isArray && currentObject.arrayData.length === 0) return
if (tempGridMode) return
const position = {
x: window.innerWidth / 2 < e.pageX ? e.pageX - 240 : e.pageX,
@ -48,7 +40,7 @@ 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) => {
@ -64,6 +56,9 @@ export default function QContextMenu(props) {
}
}
useEffect(() => {
if (!contextRef.current) return
canvasProps?.upperCanvasEl.addEventListener('contextmenu', handleContextMenu)
document.addEventListener('click', handleClick)
document.addEventListener('click', handleOutsideClick)
@ -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

@ -24,7 +24,8 @@ export default function WithDraggable({ isShow, children, pos = { x: 0, y: 0 },
<Draggable
position={{ x: position.x, y: position.y }}
onDrag={(e, data) => handleOnDrag(e, data)}
handle={handle === '' ? '.modal-handle' : handle}
handle="" //{handle === '' ? '.modal-handle' : handle} //전체 handle
cancel="input, button, select, textarea, [contenteditable], .sort-select"
>
<div className={`modal-pop-wrap ${className}`} style={{ visibility: isHidden ? 'hidden' : 'visible' }}>
{children}
@ -37,16 +38,19 @@ export default function WithDraggable({ isShow, children, pos = { x: 0, y: 0 },
)
}
function WithDraggableHeader({ title, onClose, children }) {
function WithDraggableHeader({ title, onClose, children, isFold, onFold = null }) {
return (
<div className="modal-head modal-handle">
<h1 className="title">{title}</h1>
<div className="modal-btn-wrap">
{onFold && <button className={`modal-fold ${isFold ? '' : 'act'}`} onClick={onFold}></button>}
{onClose && (
<button className="modal-close" onClick={() => onClose()}>
닫기
</button>
)}
</div>
</div>
)
}

View File

@ -0,0 +1,526 @@
import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle } from 'react'
import { createCalculator } from '@/util/calc-utils'
import '@/styles/calc.scss'
export const CalculatorInput = forwardRef(
({ value, onChange, label, options = {}, id, className = 'calculator-input', readOnly = false, placeholder, name='', disabled = false, maxLength = 12 }, ref) => {
const [showKeypad, setShowKeypad] = useState(false)
const [displayValue, setDisplayValue] = useState(value || '0')
const [hasOperation, setHasOperation] = useState(false)
const calculatorRef = useRef(createCalculator(options))
const containerRef = useRef(null)
const inputRef = useRef(null)
// ref ref
useEffect(() => {
if (ref) {
if (typeof ref === 'function') {
ref(inputRef.current)
} else {
ref.current = inputRef.current
}
}
}, [ref])
// Sync displayValue with value prop
// useEffect(() => {
// setDisplayValue(value || '0')
// }, [value])
useEffect(() => {
const newValue = value || '0'
setDisplayValue(newValue)
// value
const calculator = calculatorRef.current
if (calculator) {
//
calculator.currentOperand = newValue.toString()
calculator.previousOperand = ''
calculator.operation = undefined
setHasOperation(false)
}
}, [value])
//
useEffect(() => {
const handleClickOutside = (event) => {
if (containerRef.current && !containerRef.current.contains(event.target)) {
setShowKeypad(false)
if (hasOperation) {
// If there's an operation in progress, compute the result when losing focus
handleCompute()
}
}
}
document.addEventListener('mousedown', handleClickOutside)
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [value, onChange, hasOperation])
//
const handleNumber = (num) => {
const calculator = calculatorRef.current
let newDisplayValue = ''
// maxLength
if (maxLength > 0) {
const currentLength = (calculator.currentOperand || '').length + (calculator.previousOperand || '').length + (calculator.operation || '').length
if (currentLength >= maxLength) {
return
}
}
// 2
const shouldPreventInput = (value) => {
if (!value) return false
const decimalParts = value.toString().split('.')
return decimalParts.length > 1 && decimalParts[1].length >= 2
}
//
const appendNumber = (current, num) => {
// maxLength
if (maxLength > 0 && (current + num).length > maxLength) {
return current
}
// 0 0
if (current === '0' && num !== '.' && !current.includes('.')) {
return num.toString()
}
// 0. 0
if (current === '0' && num === '0') {
return '0.'
}
return current + num
}
if (hasOperation) {
//
if (calculator.shouldResetDisplay) {
calculator.currentOperand = num.toString()
calculator.shouldResetDisplay = false
} else if (num === '.') {
if (!calculator.currentOperand.includes('.')) {
calculator.currentOperand = calculator.currentOperand || '0' + '.'
}
} else if (!shouldPreventInput(calculator.currentOperand)) {
calculator.currentOperand = appendNumber(calculator.currentOperand || '0', num)
}
newDisplayValue = calculator.previousOperand + calculator.operation + calculator.currentOperand
setDisplayValue(newDisplayValue)
} else {
//
if (calculator.shouldResetDisplay) {
calculator.currentOperand = num.toString()
calculator.shouldResetDisplay = false
newDisplayValue = calculator.currentOperand
setDisplayValue(newDisplayValue)
if (!hasOperation) {
onChange(calculator.currentOperand)
}
} else if (num === '.') {
if (!calculator.currentOperand.includes('.')) {
calculator.currentOperand = (calculator.currentOperand || '0') + '.'
newDisplayValue = calculator.currentOperand
setDisplayValue(newDisplayValue)
if (!hasOperation) {
onChange(newDisplayValue)
}
}
} else if (!shouldPreventInput(calculator.currentOperand)) {
calculator.currentOperand = appendNumber(calculator.currentOperand || '0', num)
newDisplayValue = calculator.currentOperand
setDisplayValue(newDisplayValue)
if (!hasOperation) {
onChange(newDisplayValue)
}
}
// else {
// calculator.currentOperand = (calculator.currentOperand || '') + num
// newDisplayValue = calculator.currentOperand
// setDisplayValue(newDisplayValue)
// if (!hasOperation) {
// onChange(newDisplayValue)
// }
// }
}
//
requestAnimationFrame(() => {
if (inputRef.current) {
inputRef.current.focus()
const len = newDisplayValue.length
inputRef.current.setSelectionRange(len, len)
//
inputRef.current.scrollLeft = inputRef.current.scrollWidth
}
})
}
//
const handleOperation = (operation) => {
const calculator = calculatorRef.current
let newDisplayValue = ''
// ( )
if (!calculator.currentOperand && calculator.previousOperand) {
calculator.operation = operation
newDisplayValue = calculator.previousOperand + operation
setDisplayValue(newDisplayValue)
setHasOperation(true)
} else if (hasOperation) {
// ,
const result = calculator.compute()
if (result !== undefined) {
calculator.previousOperand = result.toString()
calculator.operation = operation
calculator.currentOperand = ''
newDisplayValue = calculator.previousOperand + operation
setDisplayValue(newDisplayValue)
}
} else {
//
calculator.previousOperand = calculator.currentOperand || '0'
calculator.operation = operation
calculator.currentOperand = ''
setHasOperation(true)
newDisplayValue = calculator.previousOperand + operation
setDisplayValue(newDisplayValue)
}
//
requestAnimationFrame(() => {
if (inputRef.current) {
inputRef.current.focus()
const len = newDisplayValue.length
inputRef.current.setSelectionRange(len, len)
//
inputRef.current.scrollLeft = inputRef.current.scrollWidth
}
})
}
// AC
const handleClear = () => {
const calculator = calculatorRef.current
const newValue = calculator.clear()
const displayValue = newValue || '0'
setDisplayValue(displayValue)
setHasOperation(false)
onChange(displayValue)
//
requestAnimationFrame(() => {
if (inputRef.current) {
inputRef.current.focus()
const len = displayValue.length
inputRef.current.setSelectionRange(len, len)
//
inputRef.current.scrollLeft = inputRef.current.scrollWidth
}
})
}
//
const handleCompute = (fromEnterKey = false) => {
const calculator = calculatorRef.current
if (!hasOperation || !calculator.currentOperand) return
const result = calculator.compute()
if (result !== undefined) {
const resultStr = result.toString()
setDisplayValue(resultStr)
setHasOperation(false)
// Only call onChange with the final result
onChange(resultStr)
//
if (!fromEnterKey) {
requestAnimationFrame(() => {
if (inputRef.current) {
inputRef.current.focus()
const len = resultStr.length
inputRef.current.setSelectionRange(len, len)
}
})
}
}
}
// DEL
const handleDelete = () => {
const calculator = calculatorRef.current
const newValue = calculator.deleteNumber()
const displayValue = newValue || '0'
setDisplayValue(displayValue)
setHasOperation(!!calculator.operation)
onChange(displayValue)
//
requestAnimationFrame(() => {
if (inputRef.current) {
inputRef.current.focus()
const len = displayValue.length
inputRef.current.setSelectionRange(len, len)
//
inputRef.current.scrollLeft = inputRef.current.scrollWidth
}
})
}
// input onChange -
const handleInputChange = (e) => {
if (readOnly) return
const inputValue = e.target.value
// (, , )
const filteredValue = inputValue.replace(/[^0-9+\-×÷.]/g, '')
//
const lastChar = filteredValue[filteredValue.length - 1]
const prevChar = filteredValue[filteredValue.length - 2]
if (['+', '×', '÷'].includes(lastChar) && ['+', '-', '×', '÷', '.'].includes(prevChar)) {
//
return
}
//
const parts = filteredValue.split(/[+\-×÷]/)
if (parts[parts.length - 1].split('.').length > 2) {
// 2
return
}
setDisplayValue(filteredValue)
//
if (filteredValue !== displayValue) {
const calculator = calculatorRef.current
const hasOperation = /[+\-×÷]/.test(filteredValue)
if (hasOperation) {
const [operand1, operator, operand2] = filteredValue.split(/([+\-×÷])/)
calculator.previousOperand = operand1 || ''
calculator.operation = operator || ''
calculator.currentOperand = operand2 || ''
setHasOperation(true)
} else {
calculator.currentOperand = filteredValue
setHasOperation(false)
// onChange
onChange(filteredValue)
}
//onChange(filteredValue)
}
}
//
const toggleKeypad = (e) => {
if (e) {
e.preventDefault()
e.stopPropagation()
}
const newShowKeypad = !showKeypad
setShowKeypad(newShowKeypad)
// Show keypad
if (newShowKeypad) {
setTimeout(() => {
inputRef.current?.focus()
}, 0)
}
}
//
const handleKeyDown = (e) => {
if (readOnly) return
// Tab
if (e.key === 'Tab') {
if (hasOperation) {
handleCompute(true) //
}
setShowKeypad(false)
return
}
//
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === 'ArrowUp' || e.key === 'ArrowDown') {
if (hasOperation) {
handleCompute(true) //
}
setShowKeypad(false)
return
}
// ( )
if (/^[0-9+\-×÷.=]$/.test(e.key) || e.key === 'Backspace' || e.key === 'Delete' || e.key === '*' || e.key === '/') {
setShowKeypad(true)
}
//
if (!showKeypad && e.key === 'Enter') {
return
}
// --- ---
if (e.key !== 'Process') { // ()
// e.preventDefault() .
}
e.preventDefault()
const calculator = calculatorRef.current
const { allowDecimal } = options
if (e.key === '.') {
// allowDecimal false
if (!allowDecimal) return
//
const currentValue = displayValue.toString()
const parts = currentValue.split(/[+\-×÷]/)
const lastPart = parts[parts.length - 1]
//
if (!lastPart.includes('.')) {
handleNumber(e.key)
}
} else if (/^[0-9]$/.test(e.key)) {
handleNumber(e.key)
} else {
switch (e.key) {
case '+':
case '-':
case '*':
case '/':
const opMap = { '*': '×', '/': '÷' }
handleOperation(opMap[e.key] || e.key)
break
case 'Enter':
case '=':
if (showKeypad) {
// :
handleCompute(true) //
setShowKeypad(false)
}
// : (preventDefault )
break
case 'Backspace':
case 'Delete':
handleDelete()
break
case 'Escape':
handleClear()
setShowKeypad(false)
break
default:
break
}
}
}
return (
<div ref={containerRef} className="calculator-input-wrapper">
{label && (
<label htmlFor={id} className="calculator-label">
{label}
</label>
)}
<input
ref={inputRef}
type="text"
id={id}
name={name}
value={displayValue}
readOnly={readOnly}
className={className}
onClick={() => !readOnly && setShowKeypad(true)}
onFocus={() => !readOnly && setShowKeypad(true)}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
tabIndex={readOnly ? -1 : 0}
placeholder={placeholder}
autoComplete={'off'}
disabled={disabled}
maxLength={maxLength}
/>
{showKeypad && !readOnly && (
<div className="keypad-container">
<div className="keypad-grid">
<button
onClick={() => {
// const newValue = calculatorRef.current.clear()
// setDisplayValue(newValue || '0')
// onChange(newValue || '0')
handleClear()
setHasOperation(false)
}}
className="btn-clear"
>
AC
</button>
<button
onClick={() => {
// const newValue = calculatorRef.current.deleteNumber()
// setDisplayValue(newValue || '0')
// onChange(newValue || '0')
//setHasOperation(!!calculatorRef.current.operation)
handleDelete()
}}
className="btn-delete"
>
DEL
</button>
<button onClick={() => handleOperation('÷')} className="btn-operator">
÷
</button>
<button onClick={() => handleOperation('×')} className="btn-operator">
×
</button>
<button onClick={() => handleOperation('-')} className="btn-operator">
-
</button>
<button onClick={() => handleOperation('+')} className="btn-operator">
+
</button>
{/* 숫자 버튼 */}
{[1, 2, 3, 4, 5, 6, 7, 8, 9].map((num) => (
<button key={num} onClick={() => handleNumber(num)} className="btn-number">
{num}
</button>
))}
{/* 0 버튼 */}
<button onClick={() => handleNumber('0')} className="btn-number btn-zero">
0
</button>
<button
onClick={() => {
const newValue = calculatorRef.current.appendNumber('.')
onChange(newValue)
}}
className="btn-number"
>
.
</button>
{/* = 버튼 */}
<button onClick={() => handleCompute(false)} className="btn-equals">
=
</button>
</div>
</div>
)}
</div>
)
},
)
CalculatorInput.displayName = 'CalculatorInput'

View File

@ -14,6 +14,7 @@ import { useMessage } from '@/hooks/useMessage'
* @param {string} targetKey - value에 있는
* @param {string} showKey - options 있는 키중 보여줄
* @param {object} params - 추가 파라미터
* @param {boolean} showFirstOptionWhenEmpty - value가 빈값일 번째 옵션을 보여줄지 여부
* @returns
*/
export default function QSelectBox({
@ -26,6 +27,8 @@ export default function QSelectBox({
targetKey = '',
showKey = '',
params = {},
tagTitle = '',
showFirstOptionWhenEmpty = false,
}) {
const { getMessage } = useMessage()
@ -38,8 +41,10 @@ export default function QSelectBox({
if (options.length === 0) return title !== '' ? title : getMessage('selectbox.title')
if (showKey !== '' && !value) {
//value showKey
// return options[0][showKey]
return title
if (showFirstOptionWhenEmpty && options.length > 0) {
return options[0][showKey]
}
return title !== '' ? title : getMessage('selectbox.title')
} else if (showKey !== '' && value) {
//value sourceKey targetKey
@ -47,12 +52,18 @@ export default function QSelectBox({
return option[sourceKey] === value[targetKey]
})
if (!option) {
if (showFirstOptionWhenEmpty && options.length > 0) {
return options[0][showKey]
}
return title !== '' ? title : getMessage('selectbox.title')
} else {
return option[showKey]
}
} else {
// .
if (showFirstOptionWhenEmpty && options.length > 0) {
return showKey !== '' ? options[0][showKey] : options[0].name
}
return title !== '' ? title : getMessage('selectbox.title')
}
}
@ -73,7 +84,7 @@ export default function QSelectBox({
useEffect(() => {
// value && handleClickSelectOption(value)
setSelected(handleInitState())
}, [options, value, sourceKey, targetKey, showKey])
}, [options, value, sourceKey, targetKey, showKey, showFirstOptionWhenEmpty])
useOnClickOutside(ref, handleClose)
@ -82,12 +93,13 @@ export default function QSelectBox({
className={`sort-select ${openSelect ? 'active' : ''} ${disabled ? 'disabled' : ''}`}
ref={ref}
onClick={disabled ? () => {} : () => setOpenSelect(!openSelect)}
title={tagTitle}
>
<p>{selected}</p>
<ul className="select-item-wrap">
<ul className="select-item-wrap" style={{ maxHeight: '200px' }}>
{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

@ -0,0 +1,213 @@
'use client'
import Link from 'next/link'
import Image from 'next/image'
import Search from '@/components/community/Search'
import Pagination from '@/components/community/Pagination'
import { useContext } from 'react'
import { useEffect, useState } from 'react'
import { useResetRecoilState, useRecoilValue, useRecoilState } from 'recoil'
import { useMessage } from '@/hooks/useMessage'
import { searchState } from '@/store/boardAtom'
import { QcastContext } from '@/app/QcastProvider'
import QnaBoardDetailModal from '@/components/community/modal/QnaDetailModal'
import { sessionStore } from '@/store/commonAtom'
import { useAxios } from '@/hooks/useAxios'
import { useCommonCode } from '@/hooks/common/useCommonCode'
export default function Qna() {
const { getMessage } = useMessage()
const resetSearch = useResetRecoilState(searchState)
const [isInitialized, setIsInitialized] = useState(false)
//const search = useRecoilValue(searchState)
const [searchForm, setSearchForm] = useRecoilState(searchState)
const { findCommonCode } = useCommonCode()
const { setIsGlobalLoading } = useContext(QcastContext)
const { get } = useAxios()
const [boardList, setBoardList] = useState([])
const [sessionState, setSessionState] = useRecoilState(sessionStore)
const [search, setSearch] = useRecoilState(searchState)
//
const [open, setOpen] = useState(false)
const [modalQnaNo, setModalQnaNo] = useState('')
const [modalQnaType, setModalQnaType] = useState('')
//
useEffect(() => {
async function fetchData() {
setIsGlobalLoading(true)
const startRow = (search.currentPage - 1) * search.pageBlock > 0 ? (search.currentPage - 1) * search.pageBlock + 1 : 1
const endRow = search.currentPage * search.pageBlock
const url = `/api/board/list`
const params = new URLSearchParams({
schNoticeTpCd : 'QC',
schNoticeClsCd: 'QNA',
compCd : 5200,
storeId : sessionState.storeId,
loginId : sessionState.userId,
schTitle : search.searchValue ? search.searchValue : '',
startRow : startRow,
endRow : endRow,
schMainYn : 'N',
siteTpCd : 'QC',
langCd : 'JA',
})
const apiUrl = `${url}?${params.toString()}`
const resultData = await get({ url: apiUrl })
if (resultData) {
if (resultData.result.code === 200) {
if (resultData.data.length > 0) {
setBoardList(resultData.data)
setSearch({ ...search, totalCount: resultData.data[0].totCnt })
} else {
setBoardList([])
setSearch({ ...search, totalCount: 0 })
}
} else {
alert(resultData.result.message)
}
}
setIsGlobalLoading(false)
}
fetchData()
}, [search.currentPage, search.searchValue, search.pageBlock, search.searchFlag])
useEffect(() => {
if (search.mainFlag === 'N') {
resetSearch()
} else {
// FAQ
setIsGlobalLoading(false)
setSearchForm({ ...searchForm, mainFlag: 'N' })
}
setIsInitialized(true)
//
// const codeL = findCommonCode(204200)
// const codeM = findCommonCode(204300)
// const codeS = findCommonCode(204400)
}, [])
if (!isInitialized) {
return null
}
const boardType = {
boardTitle: getMessage('qna.title'),
subTitle: getMessage('qna.sub.title'),
clsCode: 'QNA',
}
return (
<>
<div className="sub-header">
<div className="sub-header-inner">
<ul className="sub-header-title-wrap">
<li className="title-item">
<Link className="sub-header-title" href={'#'}>
{getMessage('qna.title')}
</Link>
</li>
</ul>
<ul className="sub-header-location">
<li className="location-item">
<span className="home">
<Image src="/static/images/main/home_icon.svg" alt="react" width={16} height={16} />
</span>
</li>
<li className="location-item">
<span>{getMessage('header.menus.community')}</span>
</li>
<li className="location-item">
<span>{getMessage('qna.title')}</span>
</li>
</ul>
</div>
</div>
<div className="sub-content">
<div className="sub-content-inner">
<div className="sub-table-box">
<Search title={boardType.boardTitle} subTitle={boardType.subTitle} isSelectUse={true} clsCode={boardType.clsCode} />
{/*<QnaTable clsCode={boardType.clsCode} />*/}
<div className="community-table">
<table>
<colgroup>
<col width={100}/>
<col width={150}/>
<col />
<col width={150}/>
<col width={150}/>
</colgroup>
<tbody>
{boardList.length > 0 ? (
boardList?.map((board) => (
<tr
key={board.qnaNo}
onClick={() => {
setOpen(true)
setModalQnaNo(board.qnaNo)
setModalQnaType("["+board?.qnaClsLrgCd+"/"+board?.qnaClsMidCd+"/"+board?.qnaClsSmlCd+"]")
}}
>
<td className="al-c">
{/* 번호 */}
{board.totCnt - board.rowNumber + 1}
</td>
{/* 답변 */}
{board?.answerYn === 'Y'? (<td className="al-c "> {getMessage('qna.list.header.answer.yes')}</td>) : (<td className="al-c org"> {getMessage('qna.list.header.answer.no')}</td>)}
<td>
<div className="mb5">[{board?.qnaClsLrgCd} / {board?.qnaClsMidCd} / {board?.qnaClsSmlCd}]</div>
{/* 제목 */}
<div className="text-frame">
<div className="text-overflow">{board.title}{board.qstTitle}</div>
{board.attachYn === 'Y' && <span className="clip"></span>}
</div>
</td>
<td>
<div className="renewal">
{/*{board.uptDt && (*/}
{/* <>*/}
{/* (<span>{getMessage('board.uptDt')}</span> : {board.uptDt})*/}
{/* </>*/}
{/*)}*/}
{board.regUserNm}
</div>
</td>
<td className="al-c">
{/* 등록일 */}
{board.regDt.split(' ')[0]}
</td>
</tr>
))
) : (
<tr>
<td className="al-c no-data" colSpan={5}>{getMessage('common.message.no.data')}</td>
</tr>
)}
</tbody>
</table>
</div>
<Pagination />
</div>
</div>
</div>
{open && <QnaBoardDetailModal qnaNo={modalQnaNo} setOpen={setOpen} qnaType = {modalQnaType} />}
</>
)
}

View File

@ -4,10 +4,11 @@ import { searchState } from '@/store/boardAtom'
import { useRecoilState, useRecoilValue } from 'recoil'
import { useState } from 'react'
import { useMessage } from '@/hooks/useMessage'
import QnaRegModal from '@/components/community/modal/QnaRegModal'
export default function Search({ title = '', subTitle = '', isSelectUse = false }) {
export default function Search({ title = '', subTitle = '', isSelectUse = false, clsCode = '' }) {
const { getMessage } = useMessage()
const [open, setOpen] = useState(false)
const search = useRecoilValue(searchState)
const [searchForm, setSearchForm] = useRecoilState(searchState)
@ -32,7 +33,13 @@ export default function Search({ title = '', subTitle = '', isSelectUse = false
} else {
setSearchView(false)
setSearchViewText('')
setSearchForm({ ...searchForm, currentPage: 1, searchValue: '', pageBlock: block, searchFlag: !searchForm.searchFlag })
setSearchForm({
...searchForm,
currentPage: 1,
searchValue: '',
pageBlock : block,
searchFlag : !searchForm.searchFlag,
})
}
//
setSearchValue('')
@ -57,7 +64,10 @@ export default function Search({ title = '', subTitle = '', isSelectUse = false
onKeyDown={handleKeyDown}
value={searchValue}
/>
<button type="button" className="community-search-ico" onClick={() => handleSearch(searchValue, selectPageBlock)}></button>
<button type="button" className="community-search-ico"
onClick={() => handleSearch(searchValue, selectPageBlock)}></button>
</div>
{searchView && (
<div className="community-search-keyword">
@ -92,6 +102,14 @@ export default function Search({ title = '', subTitle = '', isSelectUse = false
</div>
{isSelectUse && (
<div className="left-unit-box">
{clsCode === 'QNA' &&
<div>
<button className="btn-origin navy mr10"
onClick={() => {
setOpen(true)
}}> {getMessage('qna.sub.btn.inquiry')}</button>
</div>
}
<div className="select-box" style={{ width: '80px' }}>
<select
className="select-light black"
@ -112,6 +130,7 @@ export default function Search({ title = '', subTitle = '', isSelectUse = false
</div>
)}
</div>
{open && <QnaRegModal setOpen={setOpen} setReload={handleSearch} searchValue={searchValue ? searchValue : searchViewText} selectPageBlock = {selectPageBlock}/>}
</>
)
}

View File

@ -0,0 +1,128 @@
'use client'
import { useEffect, useState } from 'react'
import { useAxios } from '@/hooks/useAxios'
import { handleFileDown } from '@/util/board-utils'
import { useMessage } from '@/hooks/useMessage'
import { sessionStore } from '@/store/commonAtom'
import { useRecoilState } from 'recoil'
export default function QnaDetailModal({ qnaNo, setOpen, qnaType }) {
const { getMessage } = useMessage()
// api
const { get } = useAxios()
const [boardDetail, setBoardDetail] = useState({})
const [sessionState, setSessionState] = useRecoilState(sessionStore)
useEffect(() => {
//
const fetchDetail = async (qnaNo) => {
const url = `/api/board/detail`
const params = new URLSearchParams({
noticeNo : qnaNo,
qnaNo : qnaNo,
schNoticeClsCd: 'QNA',
compCd : 5200,
loginId : sessionState.userId,
langCd : 'JA',
siteTpCd : 'QC',
})
const apiUrl = `${url}?${params.toString()}`
const resultData = await get({ url: apiUrl })
if (resultData) {
if (resultData.result.code === 200) {
const boardDetail = resultData.data
setBoardDetail(boardDetail)
} else {
alert(resultData.result.message)
}
}
}
fetchDetail(qnaNo)
}, [])
return (
<>
<div key={qnaNo} className="modal-popup community">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<button
type="button"
className="modal-close"
onClick={() => {
setOpen(false)
}}
>
{getMessage('board.sub.btn.close')}
</button>
</div>
<div className="modal-body">
<div className="oneonone-header-wrap">
<div className="oneonone-title"> {qnaType} {boardDetail.qstTitle}</div>
<div className="oneonone-infor">
<div className="profile">{boardDetail.regUserNm}</div>
<div className="date">{boardDetail.regDt}</div>
</div>
</div>
<div className="oneonone-detail">
{boardDetail.listFile && (
<dl className="community_detail-file-wrap">
<dt>{getMessage('qna.detail.sub.fileList')}</dt>
{boardDetail.listFile.map((boardFile) => (
<dd key={boardFile.encodeFileNo}>
<button type="button" className="down" onClick={() => handleFileDown(boardFile.fileNo, 'NO')}>
{boardFile.srcFileNm}
</button>
</dd>
))}
</dl>
)}
<div
className="community_detail-inner"
dangerouslySetInnerHTML={{
__html: boardDetail.qstContents ? boardDetail.qstContents.replaceAll('\n', '<br/>') : '',
}}
></div>
</div>
{boardDetail?.answerYn === 'Y' && (
<div className="oneonone-answer">
<div className="answer-title-wrap">
<div className="answer-title">Hanwha Japan {getMessage('qna.detail.sub.answer')}</div>
<div className="oneonone-infor">
<div className="profile">{boardDetail.ansRegNm}</div>
<div className="date">{boardDetail.ansRegDt}</div>
</div>
</div>
<div
className="community_detail-inner"
dangerouslySetInnerHTML={{
__html: boardDetail.ansContents ? boardDetail.ansContents.replaceAll('\n', '<br/>') : '',
}}
></div>
{boardDetail.ansListFile && (
<dl className="community_detail-file-wrap">
<dt>{getMessage('qna.detail.sub.fileList')}</dt>
{boardDetail.ansListFile.map((boardFile) => (
<dd key={boardFile.encodeFileNo}>
<button type="button" className="down" onClick={() => handleFileDown(boardFile.fileNo, 'N')}>
{boardFile.srcFileNm}
</button>
</dd>
))}
</dl>
)}
</div>
)}
</div>
</div>
</div>
</div>
</>
)
}

View File

@ -0,0 +1,140 @@
'use client'
import { useRef } from 'react'
import { v4 as uuidv4 } from 'uuid'
import { useMessage } from '@/hooks/useMessage'
import { useSwal } from '@/hooks/useSwal'
export default function QnaFileUploader({ uploadFiles, setUploadFiles, qnaData, setQnaData }) {
const fileInputRef = useRef(null)
const { getMessage } = useMessage()
const { swalFire } = useSwal()
const handleButtonClick = (e) => {
e.preventDefault()
fileInputRef.current.click()
}
const onChangeFiles = async (e) => {
if (e.target.files.length <= 0) {
return
}
const fileList = []
let passFlag = true
const allowedFileTypes = [
'image/',
'application/pdf',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.presentationml.presentation', // PPTX
'application/vnd.ms-powerpoint', // PPT
]
Array.from(e.target.files).forEach((file) => {
//, pdf,
const fileType = file.type
if (!allowedFileTypes.some((type) => fileType.includes(type))) {
passFlag = false
} else {
fileList.push({ data: file, id: uuidv4() })
}
})
if (!passFlag) {
swalFire({ text: getMessage('estimate.detail.fileList.extCheck'), type: 'alert', icon: 'error' })
}
setUploadFiles([...uploadFiles, ...fileList])
setQnaData({...qnaData, files:[...uploadFiles, ...fileList]})
e.target.value = ''
}
const deleteFile = (id) => {
setUploadFiles(uploadFiles.filter((file) => file.id !== id))
setQnaData({...qnaData, files:uploadFiles.filter((file) => file.id !== id)})
}
const handleDrop = (e) => {
e.preventDefault()
e.stopPropagation()
const fileList = []
let passFlag = true
const allowedFileTypes = [
'image/',
'application/pdf',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.presentationml.presentation', // PPTX
'application/vnd.ms-powerpoint', // PPT
]
Array.from(e.dataTransfer.files).forEach((file) => {
//, pdf,
let fileType = file.type
if (!allowedFileTypes.some((type) => fileType.includes(type))) {
passFlag = false
} else {
fileList.push({ data: file, id: uuidv4() })
}
})
if (!passFlag) {
swalFire({ text: getMessage('estimate.detail.fileList.extCheck'), type: 'alert', icon: 'error' })
}
setUploadFiles([...uploadFiles, ...fileList])
setQnaData({...qnaData, files:[...uploadFiles, ...fileList]})
}
const handleDragOver = (e) => {
e.preventDefault()
e.stopPropagation()
}
const handleDragEnd = (e) => {
e.preventDefault()
e.stopPropagation()
}
const handleDragLeave = (e) => {
e.preventDefault()
e.stopPropagation()
}
return (
<div className="design-request-grid mt15">
<div className="design-request-count">
<div className="design-request-grid-tit">{getMessage("qna.reg.header.fileList")}</div>
<div className="btn-area one-on-one">
<label className="file-upload" htmlFor="img" onClick={handleButtonClick}>
Attach File
</label>
<input type="file" multiple name="file" ref={fileInputRef} style={{ display: 'none' }} onChange={(e) => onChangeFiles(e)} />
</div>
</div>
<div className="drag-file-box one-on-one">
<div className="drag-file-area"
draggable
onDrop={(e) => handleDrop(e)}
onDragOver={(e) => handleDragOver(e)}
onDragEnd={(e) => handleDragEnd(e)}
onDragLeave={(e) => handleDragLeave(e)}>
<p>Drag file here</p>
<ul className="file-list">
{uploadFiles.length > 0 &&
uploadFiles.map((file) => (
<li className="file-item" key={file.id}>
<span>
{file.data.name} <button className="delete" onClick={() => deleteFile(file.id)}></button>
</span>
</li>
))}
</ul>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,464 @@
'use client'
import { useMessage } from '@/hooks/useMessage'
import { sessionStore } from '@/store/commonAtom'
import { useRecoilState, useRecoilValue } from 'recoil'
import QnaFileUploader from '@/components/community/modal/QnaFileUploader'
import { useContext, useEffect, useRef, useState } from 'react'
import { useCommonCode } from '@/hooks/common/useCommonCode'
import Select from 'react-select'
import dayjs from 'dayjs'
import { useSwal } from '@/hooks/useSwal'
import { QcastContext } from '@/app/QcastProvider'
import { useAxios } from '@/hooks/useAxios'
import { globalLocaleStore } from '@/store/localeAtom'
import { e } from 'mathjs'
import { set } from 'react-hook-form'
export default function QnaRegModal({ setOpen, setReload, searchValue, selectPageBlock }) {
const { getMessage } = useMessage()
const [fileList, setFileList] = useState([])
const [sessionState, setSessionState] = useRecoilState(sessionStore)
const globalLocaleState = useRecoilValue(globalLocaleStore)
const [files, setFiles] = useState([])
//const [qnaData, setQnaData] = useState([])
const [qnaData, setQnaData] = useState({})
const [closeMdFlg, setCloseMdFlg] = useState(true)
const [closeSmFlg, setCloseSmFlg] = useState(true)
const [hideSmFlg, setHideSmFlg] = useState(false)
const qnaTypeLgCodeRef = useRef(null)
const qnaTypeMdCodeRef = useRef(null)
const qnaTypeSmCodeRef = useRef(null)
const qstMail = useRef(null);
const regUserNmRef = useRef(null)
const regUserTelNoRef = useRef(null)
const titleRef = useRef(null)
const contentsRef = useRef(null)
const { findCommonCode } = useCommonCode()
const [qnaTypeLgCodeList, setQnaTypeLgCodeList] = useState([])
const [qnaTypeMdCodeList, setQnaTypeMdCodeList] = useState([])
const [qnaTypeSmCodeList, setQnaTypeSmCodeList] = useState([])
const [phoneNumber, setPhoneNumber] = useState("");
const { swalFire } = useSwal()
const { setIsGlobalLoading } = useContext(QcastContext)
const [isBtnDisable, setIsBtnDisable] = useState(false);
const { promiseGet, post, promisePost } = useAxios(globalLocaleState)
useEffect(() => {
console.log('qnaData updated:', qnaData);
}, [qnaData]);
let fileCheck = false;
const regPhoneNumber = (e) => {
const result = e.target.value
.replace(/[^0-9.]/g, "")
//.replace(/^(\d{0,3})(\d{0,4})(\d{0,4})$/g, "$1-$2-$3")
//.replace(/(-{1,2})$/g, "");
//setPhoneNumber(result);
setQnaData({...qnaData, regUserTelNo: result })
}
const fileUploadProps = {
uploadFiles: files,
setUploadFiles: setFiles,
}
// const fileSave = (qnaData, fileUploadProps) => {
// return qnaData.files.push(fileUploadProps.uploadFiles)
// }
const initQnaReg = async () => {
qstMail.current.value = ''
regUserNmRef.current.value = ''
regUserTelNoRef.current.value = ''
qnaTypeLgCodeRef.current.setValue();
qnaTypeMdCodeRef.current.setValue();
qnaTypeSmCodeRef.current?.setValue();
titleRef.current.value = ''
contentsRef.current.value = ''
//setQnaData([])
setQnaData({
compCd: "5200",
siteTpCd: "QC",
schNoticeClsCd: "QNA",
regId: sessionState?.userId || '',
storeId: sessionState?.storeId || '',
qstMail: sessionState?.email || '',
qnaClsLrgCd: '',
qnaClsMidCd: '',
qnaClsSmlCd: ''
});
const codeL = findCommonCode(204200)
if (codeL != null) {
setQnaTypeLgCodeList(codeL)
}
setIsGlobalLoading(false)
setIsBtnDisable(false);
}
const onChangeQnaTypeL = (e) => {
if(e === undefined || e === null) return;
const codeM = findCommonCode(204300)
if (codeM != null) {
let codeList = []
codeM.map((item) => {
if(item.clRefChr1 === e.clCode) {
codeList.push(item);
}
})
setQnaTypeMdCodeList(codeList)
setQnaData({ ...qnaData, qnaClsLrgCd:e.clCode})
setCloseMdFlg(false)
qnaTypeMdCodeRef.current.setValue();
qnaTypeSmCodeRef.current?.setValue();
}
}
const onChangeQnaTypeM = (e) => {
if (!e?.clCode) return;
//
setQnaData(prevState => ({
...prevState,
qnaClsMidCd: e.clCode,
// ( )
qnaClsSmlCd: ''
}));
//
const codeS = findCommonCode(204400);
if (codeS) {
const filteredCodeList = codeS.filter(item => item.clRefChr1 === e.clCode);
setQnaTypeSmCodeList(filteredCodeList);
// ,
const hasSubCategories = filteredCodeList.length > 0;
setCloseSmFlg(!hasSubCategories);
setHideSmFlg(!hasSubCategories);
} else {
setHideSmFlg(true)
}
//
qnaTypeSmCodeRef.current?.setValue();
};
const onChangeQnaTypeS = (e) => {
if (!e?.clCode) return;
setQnaData(prevState => ({
...prevState,
qnaClsSmlCd: e.clCode
}));
}
const onFileSave = () => {
const formData= []
if(fileUploadProps.uploadFiles.length === 0) return;
if(!fileCheck) return;
fileUploadProps.uploadFiles.forEach((file) => {
//console.log("file::::::::",file)
formData.push(file)
})
setQnaData({ ...qnaData, files:formData })
fileCheck = false;
}
const isValidEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
const isEmpty = (value) => {
return value === null || value === undefined || value.trim() === "";
};
const handleQnaSubmit = async () => {
//
//console.log("1::::",qnaData)
let regUserNm = qnaData?.regUserNm??'';
if (!isValidEmail(qnaData.qstMail)) {
qstMail.current.focus();
swalFire({
title: getMessage('qna.reg.alert.require.qstMail'),
icon: 'warning',
});
return;
}
if (isEmpty(regUserNm)) {
regUserNmRef.current.value = '';
regUserNmRef.current.focus()
swalFire({
title: getMessage('qna.reg.alert.require.regUserNm'),
icon: 'warning',
})
return false
}
let qnaClsLrgCd = qnaData?.qnaClsLrgCd??'';
let qnaClsMidCd = qnaData?.qnaClsMidCd??'';
if (isEmpty(qnaClsLrgCd) || isEmpty(qnaClsMidCd) ) {
(isEmpty(qnaClsLrgCd))?qnaTypeLgCodeRef.current.focus():qnaTypeMdCodeRef.current.focus()
swalFire({
title: getMessage('qna.reg.alert.select.type'),
icon: 'warning',
})
return false
}
let title = qnaData?.title??'';
if (isEmpty(title)) {
titleRef.current.value = '';
titleRef.current.focus()
swalFire({
title: getMessage('qna.reg.alert.require.title'),
icon: 'warning',
})
return false
}
//console.log("5::::",qnaData)
let contents = qnaData?.contents??'';
if (isEmpty(contents)) {
contentsRef.current.value = '';
contentsRef.current.focus()
swalFire({
title: getMessage('qna.reg.alert.require.contents'),
icon: 'warning',
})
return false
}
const formData = new FormData()
if(qnaData?.files?.length > 0) {
qnaData?.files.forEach((file) => {
formData.append('files', file.data)
})
}
formData.append("compCd", qnaData.compCd)
formData.append("siteTpCd", qnaData.siteTpCd)
formData.append("qnaClsLrgCd", qnaData.qnaClsLrgCd)
formData.append("qnaClsMidCd", qnaData.qnaClsMidCd)
formData.append("qnaClsSmlCd", qnaData.qnaClsSmlCd)
formData.append("title", qnaData.title)
formData.append("contents", qnaData.contents)
formData.append("regId", qnaData.regId)
formData.append("storeId", qnaData.storeId)
formData.append("regUserNm", qnaData.regUserNm)
formData.append("regUserTelNo", qnaData.regUserTelNo)
formData.append("qstMail", qnaData.qstMail)
formData.append("schNoticeClsCd", qnaData.schNoticeClsCd)
//console.log(Array.from(formData));
swalFire({
html: getMessage('qna.reg.confirm.save'),
type: 'confirm',
confirmFn: async () => {
setIsBtnDisable(true);
setIsGlobalLoading(true)
try {
const apiUrl = 'api/board'
//console.log("7::::",qnaData)
await post({ url: `${apiUrl}/saveQna`, data: formData }).then((res) => {
if (res?.result.code === 200) {
//qnaData.newFileList = []
setIsGlobalLoading(false)
swalFire({ text: getMessage('qna.reg.alert.save'), type: 'alert' })
setOpen(false)
setReload(searchValue, selectPageBlock);
}else{
setIsGlobalLoading(false)
swalFire({ text: getMessage('qna.reg.alert.saveFail'), type: 'alert', icon: 'error' })
console.error('error::::::::::::', res)
}
setIsBtnDisable(false)
})
} catch (e) {
setIsGlobalLoading(false)
setIsBtnDisable(false);
console.error('error::::::::::::', e.message)
swalFire({ text: e.message, type: 'alert' , icon: 'error'})
console.error('error::::::::::::', e.message)
}
}
})
}
useEffect(() => {
initQnaReg()
},[])
// useEffect(() => {
// onFileSave()
//
// }, [onFileSave])
return (
<div className="modal-popup">
<div className="modal-dialog big">
<div className="modal-content">
<div className="modal-header">
<h1 className="title">{getMessage('qna.title')}</h1>
<button className="modal-close"
onClick={() => {
setOpen(false)
}}>{getMessage('board.sub.btn.close')}</button>
</div>
<div className="modal-body">
<div className="modal-body-inner">
<div className="design-request-table">
<div className="common-table">
<table>
<colgroup>
<col style={{ width: '100px' }} />
<col />
<col style={{ width: '120px' }} />
<col />
<col style={{ width: '150px' }} />
<col />
</colgroup>
<tbody>
<tr>
<th>{getMessage('qna.list.header.regNm')}</th>
<td><input type="text" className="input-light" value={sessionState?.userNm || ''} readOnly /></td>
<th>E-Mail<span className="red">*</span></th>
<td ><input type="text" className="input-light" required
ref={qstMail}
value={qnaData?.qstMail || ''}
onChange={(e) => setQnaData({...qnaData, qstMail: e.target.value })}
onBlur={(e) => setQnaData({ ...qnaData, qstMail: e.target.value })} />
</td>
<th>{getMessage('qna.reg.header.regDt')}</th>
<td>{dayjs(new Date()).format('YYYY-MM-DD')}</td>
</tr>
<tr>
<th>Customer</th>
<td><input type="text" className="input-light" value={sessionState?.custNm || ''} readOnly /></td>
<th>{getMessage('qna.reg.header.regUserNm')}<span className="red">*</span></th>
<td ><input type="text" className="input-light" required
ref={regUserNmRef}
value={qnaData?.regUserNm || '' }
onChange={(e) => setQnaData({...qnaData, regUserNm: e.target.value })}
onBlur={(e) => setQnaData({ ...qnaData, regUserNm: e.target.value })} /> </td>
<th>{getMessage('qna.reg.header.regUserTelNo')}</th>
<td ><input type="text" className="input-light"
ref={regUserTelNoRef}
maxLength={13}
value={qnaData?.regUserTelNo || '' }
onChange={regPhoneNumber}
/></td>
</tr>
</tbody>
</table>
</div>
</div>
<div className="design-request-grid">
<div className="design-request-count">
<div className="design-request-grid-tit">{getMessage("qna.reg.header.type")}, {getMessage("qna.reg.header.title")} <span
className="red">*</span></div>
</div>
<div className="flx-box one-on-one">
<div className="select-wrap mr5" >
<Select name="" ref={qnaTypeLgCodeRef}
options={qnaTypeLgCodeList}
placeholder="Select"
onChange={(e) => onChangeQnaTypeL(e)}
getOptionLabel={(x) => x.clCodeNm}
getOptionValue={(x) => x.clCode}
isClearable={false}
isSearchable={false}
/>
</div>
<div className="select-wrap mr5" >
<Select name="" ref={qnaTypeMdCodeRef}
options={qnaTypeMdCodeList}
placeholder="Select"
onChange={(e) => onChangeQnaTypeM(e)}
getOptionLabel={(x) => x.clCodeNm}
getOptionValue={(x) => x.clCode}
isClearable={false}
isSearchable={false}
isDisabled={closeMdFlg}
defaultValue={''}
/>
</div>
<div className="select-wrap" >
{!hideSmFlg && (
<Select name="" ref={qnaTypeSmCodeRef}
options={qnaTypeSmCodeList}
placeholder="Select"
onChange={(e) => onChangeQnaTypeS(e)}
getOptionLabel={(x) => x.clCodeNm}
getOptionValue={(x) => x.clCode}
isClearable={false}
isSearchable={false}
isDisabled={closeSmFlg}
/>)}
</div>
</div>
<div className="input-wrap mt5">
<input type="text" className="input-light" maxLength={200}
ref = {titleRef}
value={qnaData?.title || '' }
onChange={(e) => {setQnaData({ ...qnaData, title: e.target.value })}}
/>
</div>
</div>
<div className="design-request-grid mt15">
<div className="design-request-count">
<div className="design-request-grid-tit">{getMessage("qna.reg.header.contents")} <span className="red">*</span></div>
</div>
<div>
<textarea className="textarea-form" name="" id="" maxLength={4000}
ref={contentsRef}
value={qnaData?.contents || '' }
onChange={(e) => {setQnaData({ ...qnaData, contents: e.target.value })}} ></textarea>
</div>
</div>
<QnaFileUploader {...fileUploadProps} qnaData={qnaData} setQnaData={setQnaData} />
</div>
<div className="footer-btn-wrap">
{isBtnDisable === false && <button className="btn-origin navy mr5" onClick={handleQnaSubmit}>{getMessage("qna.reg.header.save")}</button>}
<button className="btn-origin grey" onClick={() => {
setOpen(false)
}}>{getMessage("board.sub.btn.close")}</button>
</div>
</div>
</div>
</div>
</div>
)
}

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'
@ -23,6 +23,8 @@ import { usePopup } from '@/hooks/usePopup'
import { useSwal } from '@/hooks/useSwal'
import { QcastContext } from '@/app/QcastProvider'
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
import {normalizeDigits, normalizeDecimal} from '@/util/input-utils'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function Estimate({}) {
const [uniqueData, setUniqueData] = useState([])
const [handlePricingFlag, setHandlePricingFlag] = useState(false)
@ -60,7 +62,7 @@ export default function Estimate({}) {
const [cableItemList, setCableItemList] = useState([]) //
const [cableItem, setCableItem] = useState('') //
const [cableDbItem, setCableDbItem] = useState('') //
const [startDate, setStartDate] = useState(new Date())
const singleDatePickerProps = {
startDate,
@ -98,7 +100,7 @@ export default function Estimate({}) {
}
const initEstimate = (currPid = currentPid) => {
console.log('🚀 ~ initEstimate ~ currPid:', currPid)
// console.log('🚀 ~ initEstimate ~ currPid:', currPid)
closeAll()
setObjectNo(objectRecoil.floorPlanObjectNo)
@ -117,6 +119,7 @@ export default function Estimate({}) {
item.value = item.clRefChr1
item.label = item.clRefChr2
})
// console.log(code2)
setCableItemList(code2)
}
@ -136,7 +139,27 @@ export default function Estimate({}) {
updatedRes = [...res]
}
setOriginDisplayItemList(res)
const groupByItemGroup = (items) => {
const grouped = items.reduce((acc, item) => {
const group = item.itemGroup || '기타';
if (!acc[group]) {
acc[group] = {
label: group,
options: []
};
}
acc[group].options.push({
value: item.itemId,
label: `${item.itemNo} - ${item.itemName}`,
...item
});
return acc;
}, {});
return Object.values(grouped);
};
const groupedItems = groupByItemGroup(res);
setOriginDisplayItemList(groupedItems)
setDisplayItemList(updatedRes)
}
})
@ -151,9 +174,22 @@ export default function Estimate({}) {
})
}
const groupStyles = {
groupHeading: (provided) => ({
...provided,
fontSize: '14px',
fontWeight: 'bold',
color: '#333',
backgroundColor: '#f5f5f5',
padding: '8px 12px',
marginBottom: '4px',
borderBottom: '2px solid #ddd'
})
};
useEffect(() => {
console.log('🚀 ~ Estimate ~ selectedPlan:', selectedPlan)
if (selectedPlan) initEstimate(selectedPlan.planNo)
// console.log('🚀 ~ Estimate ~ selectedPlan:', selectedPlan)
if (selectedPlan) initEstimate(selectedPlan?.planNo?? currentPid)
}, [selectedPlan])
useEffect(() => {
@ -175,7 +211,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 +256,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 +282,6 @@ export default function Estimate({}) {
}
}
})
setSpecialNoteList(res)
setSpecialNoteFirstFlg(true)
@ -377,8 +418,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 +428,8 @@ export default function Estimate({}) {
if (isNotEmptyArray(res?.data)) {
setStorePriceList(res.data)
}
setItemChangeYn(true)
})
if (estimateContextState.estimateType === 'YJSS') {
@ -416,8 +459,6 @@ export default function Estimate({}) {
handlePricing('UNIT_PRICE')
}
}
setItemChangeYn(true)
}
}, [estimateContextState?.estimateType])
@ -469,6 +510,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
}
}
}
})
@ -478,12 +534,27 @@ export default function Estimate({}) {
})
}
//Pricing confirm
const handlePricingBtn = (showPriceCd) => {
swalFire({
text: getMessage('estimate.detail.showPrice.pricingBtn.confirm'),
type: 'confirm',
icon: 'warning',
confirmFn: () => {
handlePricing(showPriceCd)
setEstimateContextState({ pricingFlag:true })
},
})
}
//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 +577,6 @@ export default function Estimate({}) {
})
}
}
setIsGlobalLoading(true)
await promisePost({ url: '/api/estimate/price/item-price-list', data: param }).then((res) => {
let updateList = []
@ -531,6 +601,7 @@ export default function Estimate({}) {
updateList.push({
...item,
openFlg: data.data2[i].unitPrice === '0.0' ? '1' : '0',
unitOpenFlg: (showPriceCd === 'QSP_PRICE' && item.openFlg === '1') ? '1' : '0',
salePrice: data.data2[i].unitPrice === null ? '0' : data.data2[i].unitPrice,
saleTotPrice: (item.amount * data.data2[i].unitPrice).toString(),
})
@ -609,11 +680,14 @@ export default function Estimate({}) {
newValue = parts[0] + '.' + parts[1].substring(0, 2)
}
let pkgAsp = newValue || '0'
let pkgAsp = normalizeDecimal(newValue || '0')
// PKG
let totVolKw = estimateContextState.totVolKw * 1000
let pkgTotPrice = parseFloat(pkgAsp?.replaceAll(',', '')) * totVolKw * 1000
// let pkgTotPrice = parseFloat(pkgAsp?.replaceAll(',', '')) * totVolKw * 1000
const pkgAspNumber = Number(normalizeDecimal(pkgAsp))
const pkgTotPrice = pkgAspNumber * totVolKw * 1000
setEstimateContextState({
pkgAsp: pkgAsp,
@ -627,7 +701,7 @@ export default function Estimate({}) {
//
const onChangeAmount = (value, dispOrder, index) => {
//itemChangeFlg = 1, partAdd = 0
let amount = Number(value.replace(/[^0-9]/g, '').replaceAll(',', ''))
let amount = Number(normalizeDigits(value))
if (isNaN(amount)) {
amount = '0'
@ -665,7 +739,8 @@ export default function Estimate({}) {
//
const onChangeSalePrice = (value, dispOrder, index) => {
//itemChangeFlg, partAdd
let salePrice = Number(value.replace(/[^0-9]/g, '').replaceAll(',', ''))
let salePrice = Number(normalizeDecimal(value))
if (isNaN(salePrice)) {
salePrice = 0
} else {
@ -696,7 +771,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' && item.itemTpCd !== 'S13') {
if (value !== '') {
onChangeDisplayItem(value, item.dispOrder, index, true)
}
@ -705,6 +780,18 @@ export default function Estimate({}) {
setCableItem(value)
}
/* 케이블 select 변경시 */
const onChangeDisplayDoubleCableItem = (value, itemList) => {
itemList.map((item, index) => {
if (item.dispCableFlg === '1' && (item.itemTpCd === 'M12' || item.itemTpCd === 'S13')) {
if (value !== '') {
onChangeDisplayItem(value, item.dispOrder, index, true)
}
}
})
setCableDbItem(value)
}
// /
const onChangeDisplayItem = (itemId, dispOrder, index, flag) => {
const param = {
@ -892,7 +979,7 @@ export default function Estimate({}) {
delete item.showSalePrice
delete item.showSaleTotPrice
if (item.delFlg === '0') {
let amount = Number(item.amount?.replace(/[^0-9]/g, '').replaceAll(',', '')) || 0
let amount = Number(normalizeDigits(item.amount)) || 0
let price
if (amount === 0) {
price = 0
@ -927,7 +1014,7 @@ export default function Estimate({}) {
makeUniqueSpecialNoteCd(itemList)
itemList.forEach((item) => {
if (item.delFlg === '0') {
let amount = Number(item.amount?.replace(/[^0-9]/g, '').replaceAll(',', '')) || 0
let amount = Number(normalizeDigits(item.amount)) || 0
let salePrice
if (item.moduleFlg === '1') {
const volKw = (item.pnowW * amount) / 1000
@ -961,8 +1048,8 @@ export default function Estimate({}) {
}
}
})
let pkgAsp = estimateContextState.pkgAsp ? Number(estimateContextState.pkgAsp.replaceAll(',', '')) : 0
//let pkgAsp = estimateContextState.pkgAsp ? Number(estimateContextState.pkgAsp.replaceAll(',', '')) : 0
const pkgAsp = Number(normalizeDecimal(estimateContextState.pkgAsp))
totals.pkgTotPrice = pkgAsp * totals.totVolKw * 1000
totals.supplyPrice = totals.addSupplyPrice + totals.pkgTotPrice
totals.vatPrice = totals.supplyPrice * 0.1
@ -1021,7 +1108,7 @@ export default function Estimate({}) {
let dispCableFlgCnt = 0
estimateContextState.itemList.forEach((item) => {
if (item.delFlg === '0') {
let amount = Number(item.amount?.replace(/[^0-9]/g, '').replaceAll(',', '')) || 0
let amount = Number(normalizeDigits(item.amount)) || 0
let salePrice
if (item.moduleFlg === '1') {
const volKw = (item.pnowW * amount) / 1000
@ -1056,18 +1143,24 @@ export default function Estimate({}) {
if (item.dispCableFlg === '1') {
dispCableFlgCnt++
if(item.itemTpCd === 'M12' || item.itemTpCd === 'S13') {
setCableDbItem(item.itemId)
}else{
setCableItem(item.itemId)
}
}
}
})
if (dispCableFlgCnt === 0) {
setCableItem('100038')
setCableDbItem('100037')
}
let pkgAsp = estimateContextState.pkgAsp ? Number(estimateContextState.pkgAsp.replaceAll(',', '')) : 0
// let pkgAsp = estimateContextState.pkgAsp ? Number(estimateContextState.pkgAsp.replaceAll(',', '')) : 0
const pkgAsp = Number(normalizeDecimal(estimateContextState.pkgAsp))
totals.pkgTotPrice = pkgAsp * totals.totVolKw * 1000
totals.supplyPrice = totals.addSupplyPrice + totals.pkgTotPrice
totals.vatPrice = totals.supplyPrice * 0.1
totals.totPrice = totals.supplyPrice + totals.vatPrice
@ -1097,7 +1190,7 @@ export default function Estimate({}) {
delete item.showSalePrice
delete item.showSaleTotPrice
if (item.delFlg === '0') {
let amount = Number(item.amount?.replace(/[^0-9]/g, '').replaceAll(',', '')) || 0
let amount = Number(normalizeDigits(item.amount)) || 0
let price
if (amount === 0) {
price = 0
@ -1123,16 +1216,18 @@ export default function Estimate({}) {
if (item.dispCableFlg === '1') {
dispCableFlgCnt++
}
if (item.dispCableFlg === '1') {
if(item.itemTpCd === 'M12' || item.itemTpCd === 'S13') {
setCableDbItem(item.itemId)
}else{
setCableItem(item.itemId)
}
}
}
})
if (dispCableFlgCnt === 0) {
setCableItem('100038')
setCableDbItem('100037')
}
totals.vatPrice = totals.supplyPrice * 0.1
@ -1193,10 +1288,26 @@ export default function Estimate({}) {
if (dispCableFlgCnt === 0) {
setCableItem('100038')
setCableDbItem('100037')
}
}
}, [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">
@ -1207,7 +1318,7 @@ export default function Estimate({}) {
<div className="estimate-box">
<div className="estimate-tit">{getMessage('estimate.detail.objectNo')}</div>
<div className="estimate-name">
{currentObjectNo} (Plan No: {planNo})
{currentObjectNo} (Plan No: {currentPid})
</div>
</div>
<div className="estimate-box">
@ -1341,21 +1452,32 @@ export default function Estimate({}) {
{getMessage('estimate.detail.estimateType')} <span className="important">*</span>
</th>
<td colSpan={3}>
<div className="form-flex-wrap">
<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 })
{/*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>
>
{/*<input*/}
{/* type="radio"*/}
{/* name="estimateType"*/}
{/* id="YJSS"*/}
{/* value={'YJSS'}*/}
{/* checked={estimateContextState?.estimateType === 'YJSS' ? true : false}*/}
{/* onChange={(e) => {*/}
{/* //주문분류*/}
{/* setHandlePricingFlag(true)*/}
{/* setEstimateContextState({ estimateType: e.target.value, setEstimateContextState })*/}
{/* }}*/}
{/*/>*/}
{/*<label htmlFor="YJSS">{getMessage('estimate.detail.estimateType.yjss')}</label>*/}
</div>
<div className="d-check-radio light">
<input
@ -1372,6 +1494,38 @@ export default function Estimate({}) {
<label htmlFor="YJOD">{getMessage('estimate.detail.estimateType.yjod')}</label>
</div>
</div>
{session?.storeLvl === '100000' && agencyCustList.length > 0 ? ( // 1 => 100000
<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>
<tr>
@ -1540,13 +1694,13 @@ export default function Estimate({}) {
{/* 파일첨부 끝 */}
{/* 견적특이사항 시작 */}
<div className="table-box-title-wrap">
<div className="title-wrap">
<h3 className="product">{getMessage('estimate.detail.header.specialEstimate')}</h3>
<div className="estimate-check-btn">
<button className={`estimate-arr-btn down mr5 ${hidden ? '' : 'on'}`} onClick={() => setHidden(false)}></button>
<button className={`estimate-arr-btn up ${hidden ? 'on' : ''}`} onClick={() => setHidden(true)}></button>
</div>
</div>
{/*<div className="title-wrap">*/}
{/* <h3 className="product">{getMessage('estimate.detail.header.specialEstimate')}</h3>*/}
{/* <div className="estimate-check-btn">*/}
{/* <button className={`estimate-arr-btn down mr5 ${hidden ? '' : 'on'}`} onClick={() => setHidden(false)}></button>*/}
{/* <button className={`estimate-arr-btn up ${hidden ? 'on' : ''}`} onClick={() => setHidden(true)}></button>*/}
{/* </div>*/}
{/*</div>*/}
</div>
{/* 견적 특이사항 코드영역시작 */}
<div className={`estimate-check-wrap ${hidden ? 'hide' : ''}`}>
@ -1729,15 +1883,17 @@ export default function Estimate({}) {
<button
type="button"
className="btn-origin grey ml5"
onClick={() => {
onClick={(event) => {
setHandlePricingFlag(true)
handlePricing(showPriceCd)
handlePricingBtn(showPriceCd)
}}
>
{getMessage('estimate.detail.showPrice.pricingBtn')}
</button>
</div>
<div className="product-price-wrap ml10">
<div className="product-price-tit">{getMessage('estimate.detail.header.singleCable')}</div>
<div className="select-wrap">
<select
className="select-light"
@ -1747,11 +1903,34 @@ export default function Estimate({}) {
value={cableItem}
>
{cableItemList.length > 0 &&
cableItemList.map((row) => (
<option key={row.clRefChr1} value={row.clRefChr1}>
cableItemList.map((row) => {
if(!row.clRefChr2.includes('S')){
return <option key={row.clRefChr1} value={row.clRefChr1}>
{row.clRefChr2}
</option>
))}
}
})}
</select>
</div>
</div>
<div className="product-price-wrap ml10">
<div className="product-price-tit">{getMessage('estimate.detail.header.doubleCable')}</div>
<div className="select-wrap">
<select
className="select-light"
onChange={(e) => {
onChangeDisplayDoubleCableItem(e.target.value, estimateContextState.itemList)
}}
value={cableDbItem}
>
{cableItemList.length > 0 &&
cableItemList.map((row) => {
if(row.clRefChr2.includes('S')){
return <option key={row.clRefChr1} value={row.clRefChr1}>
{row.clRefChr2.replace('S','')}
</option>
}
})}
</select>
</div>
</div>
@ -1834,7 +2013,7 @@ export default function Estimate({}) {
<input
type="checkbox"
id={item?.dispOrder}
disabled={!!item?.paDispOrder || item.dispCableFlg === '1'}
disabled={!!item?.paDispOrder || item.dispCableFlg === '1X'}
onChange={() => onChangeSelect(item.dispOrder)}
checked={!!selection.has(item.dispOrder)}
/>
@ -1853,14 +2032,20 @@ export default function Estimate({}) {
classNamePrefix="custom"
placeholder="Select"
options={originDisplayItemList}
styles={groupStyles}
onChange={(e) => {
if (isObjectNotEmpty(e)) {
onChangeDisplayItem(e.itemId, item.dispOrder, index, false)
}
}}
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 +2065,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.itemTpCd === 'S13')? 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.itemTpCd === 'S13' )? item.itemId : option.clRefChr1 === item.itemId
})}
/>
)}
@ -1917,15 +2107,27 @@ export default function Estimate({}) {
</td>
<td>
<div className="input-wrap" style={{ width: '100%' }}>
<input
type="text"
className="input-light al-r"
{/*<input*/}
{/* type="text"*/}
{/* className="input-light al-r"*/}
{/* value={convertNumberToPriceDecimal(item?.amount?.replaceAll(',', ''))}*/}
{/* disabled={item.itemId === '' || !!item?.paDispOrder}*/}
{/* onChange={(e) => {*/}
{/* onChangeAmount(e.target.value, item.dispOrder, index)*/}
{/* }}*/}
{/* maxLength={6}*/}
{/*/>*/}
<CalculatorInput
className={"input-light al-r"}
value={convertNumberToPriceDecimal(item?.amount?.replaceAll(',', ''))}
disabled={item.itemId === '' || !!item?.paDispOrder}
onChange={(e) => {
onChangeAmount(e.target.value, item.dispOrder, index)
onChange={(value) =>{
onChangeAmount(value, item.dispOrder, index)
}}
options={{
allowNegative: false,
allowDecimal: false
}}
maxLength={6}
/>
</div>
</td>
@ -1933,10 +2135,37 @@ export default function Estimate({}) {
<td>
<div className="form-flex-wrap">
<div className="input-wrap mr5">
<input
type="text"
className="input-light al-r"
value={convertNumberToPriceDecimal(item?.showSalePrice === '0' ? null : item?.salePrice?.replaceAll(',', ''))}
{/*<input*/}
{/* type="text"*/}
{/* className="input-light al-r"*/}
{/* value={*/}
{/* item.openFlg === '1'*/}
{/* ? 'OPEN'*/}
{/* : convertNumberToPriceDecimal(item?.showSalePrice === '0' ? null : item?.salePrice?.replaceAll(',', ''))*/}
{/* }*/}
{/* disabled={*/}
{/* item.openFlg === '1'*/}
{/* ? true*/}
{/* : estimateContextState?.estimateType === 'YJSS'*/}
{/* ? item?.paDispOrder*/}
{/* ? true*/}
{/* : item.pkgMaterialFlg !== '1'*/}
{/* : item.itemId === '' || !!item?.paDispOrder*/}
{/* ? true*/}
{/* : item.openFlg === '1'*/}
{/* }*/}
{/* onChange={(e) => {*/}
{/* onChangeSalePrice(e.target.value, item.dispOrder, index)*/}
{/* }}*/}
{/* maxLength={12}*/}
{/*/>*/}
<CalculatorInput
className={"input-light al-r"}
value={
item.openFlg === '1'
? 'OPEN'
: convertNumberToPriceDecimal(item?.showSalePrice === '0' ? null : item?.salePrice?.replaceAll(',', ''))
}
disabled={
item.openFlg === '1'
? true
@ -1947,13 +2176,15 @@ export default function Estimate({}) {
: item.itemId === '' || !!item?.paDispOrder
? true
: item.openFlg === '1'
? true
: false
}
onChange={(e) => {
onChangeSalePrice(e.target.value, item.dispOrder, index)
onChange={(value) =>{
onChangeSalePrice(value, item.dispOrder, index)
}}
maxLength={12}
options={{
allowNegative: false,
allowDecimal: false
}}
/>
</div>
{item.openFlg === '1' && (
@ -1964,7 +2195,9 @@ export default function Estimate({}) {
</div>
</td>
<td className="al-r">
{convertNumberToPriceDecimal(
{item?.openFlg === '1'
? 'OPEN'
: convertNumberToPriceDecimal(
item?.showSaleTotPrice === '0'
? null
: item?.amount === ''

View File

@ -6,6 +6,7 @@ import { useRecoilValue } from 'recoil'
import { floorPlanObjectState, estimateState } from '@/store/floorPlanObjectAtom'
import { usePathname, useSearchParams } from 'next/navigation'
import { QcastContext } from '@/app/QcastProvider'
import { sessionStore } from '@/store/commonAtom'
export default function DocDownOptionPop({ planNo, setEstimatePopupOpen, docDownPopLockFlg }) {
const { setIsGlobalLoading } = useContext(QcastContext)
@ -30,7 +31,7 @@ export default function DocDownOptionPop({ planNo, setEstimatePopupOpen, docDown
// recoil
const objectRecoil = useRecoilValue(floorPlanObjectState)
const estimateRecoilState = useRecoilValue(estimateState)
const sessionState = useRecoilValue(sessionStore)
//
const handleFileDown = async () => {
const url = '/api/estimate/excel-download'
@ -67,6 +68,8 @@ export default function DocDownOptionPop({ planNo, setEstimatePopupOpen, docDown
schWeightFlg: schWeightFlg,
schDrawingFlg: defaultSchDrawingFlg,
pwrGnrSimType: 'D', //default
userId: sessionState.userId ? sessionState.userId : "",
saleStoreId: sessionState.storeId ? sessionState.storeId : "",
}
const options = { responseType: 'blob' }
@ -130,7 +133,7 @@ export default function DocDownOptionPop({ planNo, setEstimatePopupOpen, docDown
<div className="common-table">
<table>
<colgroup>
<col style={{ width: '260px' }} />
<col style={{ width: '220px' }} />
<col />
</colgroup>
<tbody>
@ -183,7 +186,7 @@ export default function DocDownOptionPop({ planNo, setEstimatePopupOpen, docDown
/>
<label htmlFor="schUnitPricePdfFlg0">{getMessage('estimate.detail.docPopup.schUnitPriceFlg.pdfFlg0')}</label>
</div>
<div className="d-check-radio light ">
<div className="d-check-radio light mr10">
<input
type="radio"
id="schUnitPricePdfFlg1"
@ -197,6 +200,20 @@ export default function DocDownOptionPop({ planNo, setEstimatePopupOpen, docDown
/>
<label htmlFor="schUnitPricePdfFlg1">{getMessage('estimate.detail.docPopup.schUnitPriceFlg.pdfFlg1')}</label>
</div>
<div className="d-check-radio light">
<input
type="radio"
id="schUnitPriceExcelFlg2"
name="schUnitPriceFlg"
value={'4'}
checked={schUnitPriceFlg === '4'}
onChange={(e) => {
setSchDownload('EXCEL2')
setSchUnitPriceFlg(e.target.value)
}}
/>
<label htmlFor="schUnitPriceExcelFlg2">{getMessage('estimate.detail.docPopup.schUnitPriceFlg.excelFlg2')}</label>
</div>
</div>
</td>
</tr>

View File

@ -1,6 +1,8 @@
import { fabric } from 'fabric'
import { v4 as uuidv4 } from 'uuid'
import { getDirectionByPoint } from '@/util/canvas-util'
import { calcLinePlaneSize } from '@/util/qpolygon-utils'
import { logger } from '@/util/logger'
export const QLine = fabric.util.createClass(fabric.Line, {
type: 'QLine',
@ -14,10 +16,11 @@ export const QLine = fabric.util.createClass(fabric.Line, {
children: [],
padding: 5,
textVisible: true,
textBaseline: 'alphabetic',
initialize: function (points, options, length = 0) {
// 소수점 전부 제거
points = points.map((point) => Number(point?.toFixed(1)))
points = points.map((point) => Number(Number(point)?.toFixed(1)))
this.callSuper('initialize', points, { ...options, selectable: options.selectable ?? true })
if (options.id) {
@ -31,14 +34,16 @@ export const QLine = fabric.util.createClass(fabric.Line, {
this.direction = options.direction ?? getDirectionByPoint({ x: this.x1, y: this.y1 }, { x: this.x2, y: this.y2 })
this.textMode = options.textMode ?? 'plane' // plane:복시도, actual:실측, none:표시안함
this.textVisible = options.textVisible ?? true
if (length !== 0) {
this.length = length
} else {
this.setLength()
}
this.startPoint = { x: this.x1, y: this.y1 }
this.endPoint = { x: this.x2, y: this.y2 }
try {
this.setLength()
} catch (e) {
setTimeout(() => {
this.setLength()
}, 100)
}
},
init: function () {
@ -66,31 +71,29 @@ export const QLine = fabric.util.createClass(fabric.Line, {
},
setLength() {
if (this.attributes?.actualSize !== undefined && this.attributes?.planeSize !== undefined) {
if (this.textMode === 'plane') {
this.length = this.attributes.planeSize / 10
} else if (this.textMode === 'actual') {
this.length = this.attributes.actualSize / 10
}
} else {
const scaleX = this.scaleX
const scaleY = this.scaleY
const x1 = this.left
const y1 = this.top
const x2 = this.left + this.width * scaleX
const y2 = this.top + this.height * scaleY
const dx = x2 - x1
const dy = y2 - y1
this.length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(1))
// Ensure all required properties are valid numbers
const { x1, y1, x2, y2 } = this
if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2)) {
logger.error('Invalid coordinates in QLine:', { x1, y1, x2, y2 })
this.length = 0
return
}
this.length = calcLinePlaneSize({ x1, y1, x2, y2 }) / 10
},
setLengthByValue(length) {
this.length = length / 10
},
addLengthText() {
const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id)
if (this.textMode === 'none') {
if (thisText) {
this.canvas.remove(thisText)
if (this.attributes?.actualSize) {
thisText.set({ actualSize: this.attributes.actualSize })
}
if (this.attributes?.planeSize) {
thisText.set({ planeSize: this.attributes.planeSize })
}
} else {
this.setLength()
@ -101,11 +104,6 @@ export const QLine = fabric.util.createClass(fabric.Line, {
const x2 = this.left + this.width * scaleX
const y2 = this.top + this.height * scaleY
if (thisText) {
thisText.set({ text: this.getLength().toString(), left: (x1 + x2) / 2, top: (y1 + y2) / 2 })
this.text = thisText
return
}
let left, top
if (this.direction === 'left' || this.direction === 'right') {
left = (x1 + x2) / 2
@ -122,6 +120,8 @@ export const QLine = fabric.util.createClass(fabric.Line, {
const degree = (Math.atan2(y2 - y1, x2 - x1) * 180) / Math.PI
const text = new fabric.Textbox(this.getLength().toString(), {
actualSize: this.attributes?.actualSize,
planeSize: this.attributes?.planeSize,
left: left,
top: top,
fontSize: this.fontSize,
@ -182,4 +182,78 @@ export const QLine = fabric.util.createClass(fabric.Line, {
}
return this
},
setCoords: function () {
// 부모 클래스의 setCoords 호출
this.callSuper('setCoords')
// QLine의 경우 추가 처리 - 항상 강제로 재계산
if (this.canvas) {
// 모든 좌표 관련 캐시 초기화
delete this.oCoords
delete this.aCoords
delete this.__corner
// 다시 부모 setCoords 호출
this.callSuper('setCoords')
// 한 번 더 강제로 bounding rect 재계산
this._clearCache && this._clearCache()
}
},
containsPoint: function (point) {
// 먼저 좌표 업데이트
this.setCoords()
// 캔버스 줌과 viewport transform 고려한 좌표 변환
let localPoint = point
if (this.canvas) {
const vpt = this.canvas.viewportTransform
if (vpt) {
// viewport transform 역변환
const inverted = fabric.util.invertTransform(vpt)
localPoint = fabric.util.transformPoint(point, inverted)
}
}
// 기본 boundingRect 사용하되 줌을 고려하여 선택 영역 조정
const boundingRect = this.getBoundingRect(true)
// 선의 방향 판단
const dx = Math.abs(this.x2 - this.x1)
const dy = Math.abs(this.y2 - this.y1)
const isVertical = dx < dy && dx < 10
const isDiagonal = dx > 10 && dy > 10
// 줌 레벨에 따른 선택 영역 조정
const zoom = this.canvas ? this.canvas.getZoom() : 1
const baseMultiplier = 1 // 줌이 클수록 선택 영역을 줄임
let reducedWidth, reducedHeight
if (isDiagonal) {
reducedWidth = Math.max(boundingRect.width / 2, 10 * baseMultiplier)
reducedHeight = Math.max(boundingRect.height / 2, 10 * baseMultiplier)
} else if (isVertical) {
reducedWidth = Math.max(boundingRect.width * 2, 20 * baseMultiplier)
reducedHeight = boundingRect.height
} else {
reducedWidth = boundingRect.width
reducedHeight = Math.max(boundingRect.height * 2, 20 * baseMultiplier)
}
// 축소된 영역의 중심점 계산
const centerX = boundingRect.left + boundingRect.width / 2
const centerY = boundingRect.top + boundingRect.height / 2
// 축소된 영역의 경계 계산
const left = centerX - reducedWidth / 2
const top = centerY - reducedHeight / 2
const right = centerX + reducedWidth / 2
const bottom = centerY + reducedHeight / 2
// 점이 축소된 영역 내에 있는지 확인
return localPoint.x >= left && localPoint.x <= right && localPoint.y >= top && localPoint.y <= bottom
},
})

View File

@ -2,10 +2,11 @@ import { fabric } from 'fabric'
import { v4 as uuidv4 } from 'uuid'
import { QLine } from '@/components/fabric/QLine'
import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util'
import { calculateAngle, drawGabledRoof, drawRidgeRoof, drawShedRoof, toGeoJSON } from '@/util/qpolygon-utils'
import { calculateAngle, drawGableRoof, drawRoofByAttribute, drawShedRoof, toGeoJSON } from '@/util/qpolygon-utils'
import * as turf from '@turf/turf'
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
import Big from 'big.js'
import { drawSkeletonRidgeRoof } from '@/util/skeleton-utils'
export const QPolygon = fabric.util.createClass(fabric.Polygon, {
type: 'QPolygon',
@ -29,12 +30,14 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
this.texts = []
this.hips = []
this.ridges = []
this.connectRidges = []
this.cells = []
this.innerLines = []
this.children = []
this.separatePolygon = []
this.toFixed = options.toFixed ?? 1
this.baseLines = []
this.adjustRoofLines = []
// this.colorLines = []
// 소수점 전부 제거
points.forEach((point) => {
@ -45,8 +48,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 +68,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
if (!isDiagonal) {
points = sortedPoints(points)
this.isSortedPoints = true
}
}
@ -82,6 +89,10 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
this.initLines()
this.init()
this.setShape()
const originWidth = this.originWidth ?? this.width
const originHeight = this.originHeight ?? this.height
this.originWidth = this.angle === 90 || this.angle === 270 ? originHeight : originWidth
this.originHeight = this.angle === 90 || this.angle === 270 ? originWidth : originHeight
},
setShape() {
@ -119,11 +130,15 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
this.addLengthText()
this.on('moving', () => {
this.initLines()
this.addLengthText()
this.setCoords()
})
this.on('modified', (e) => {
this.on('modified', () => {
this.initLines()
this.addLengthText()
this.setCoords()
})
this.on('selected', () => {
@ -166,8 +181,27 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
return fabric.util.transformPoint(p, matrix)
})
this.points = transformedPoints
const { left, top } = this.calcOriginCoords()
this.set('pathOffset', { x: left, y: top })
// 바운딩 박스 재계산 (width, height 업데이트 - fill 영역 수정)
const calcDim = this._calcDimensions({})
this.width = calcDim.width
this.height = calcDim.height
const newPathOffset = {
x: calcDim.left + this.width / 2,
y: calcDim.top + this.height / 2,
}
this.set('pathOffset', newPathOffset)
// 변환을 points에 적용했으므로 left, top, angle, scale 모두 리셋 (이중 변환 방지)
this.set({
left: newPathOffset.x,
top: newPathOffset.y,
angle: 0,
scaleX: 1,
scaleY: 1,
})
this.setCoords()
this.initLines()
})
@ -183,8 +217,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,
@ -203,14 +237,50 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
line.startPoint = point
line.endPoint = nextPoint
this.lines.push(line)
this.calculateDegree()
})
},
calculateDegree() {
const degrees = []
// polygon.lines를 순회하며 각도를 구해 출력
this.lines.forEach((line) => {
const dx = line.x2 - line.x1
const dy = line.y2 - line.y1
const rad = Math.atan2(dy, dx)
const degree = (rad * 180) / Math.PI
degrees.push(degree)
})
function isMultipleOf45(degree, epsilon = 1) {
return Math.abs(degree % 45) <= epsilon || Math.abs((degree % 45) - 45) <= epsilon
}
this.isMultipleOf45 = degrees.every((degree) => isMultipleOf45(degree))
},
/**
* 보조선 그리기
* @param settingModalFirstOptions
*/
drawHelpLine(settingModalFirstOptions) {
/* innerLines 초기화 */
this.canvas
.getObjects()
.filter(
(obj) =>
obj.parentId === this.id &&
obj.name !== POLYGON_TYPE.WALL &&
obj.name !== POLYGON_TYPE.ROOF &&
obj.name !== 'lengthText' &&
obj.name !== 'outerLine' &&
obj.name !== 'baseLine',
// && obj.name !== 'outerLinePoint',
)
.forEach((obj) => this.canvas.remove(obj))
this.innerLines = []
this.adjustRoofLines = []
this.canvas.renderAll()
let textMode = 'plane'
const dimensionDisplay = settingModalFirstOptions?.dimensionDisplay.find((opt) => opt.selected).id
@ -228,61 +298,80 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
break
}
const types = []
this.lines.forEach((line) => types.push(line.attributes.type))
const types = this.lines.map((line) => line.attributes.type)
const eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE]
const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD]
const isGableRoof = function (types) {
if (!types.includes(LINE_TYPE.WALLLINE.GABLE)) {
return false
}
const gableTypes = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD]
const oddTypes = types.filter((type, i) => i % 2 === 0)
const evenTypes = types.filter((type, i) => i % 2 === 1)
// const isEaves = types.every((type) => eavesType.includes(type))
const gableOdd = types.filter((type, i) => i % 2 === 0)
const gableEven = types.filter((type, i) => i % 2 === 1)
const hasShed = types.includes(LINE_TYPE.WALLLINE.SHED)
const oddAllEaves = oddTypes.every((type) => type === LINE_TYPE.WALLLINE.EAVES)
const evenAllGable = evenTypes.every((type) => gableTypes.includes(type))
const evenAllEaves = evenTypes.every((type) => type === LINE_TYPE.WALLLINE.EAVES)
const oddAllGable = oddTypes.every((type) => gableTypes.includes(type))
// A형, B형 박공 지붕
if (
(gableOdd.every((type) => type === LINE_TYPE.WALLLINE.EAVES) && gableEven.every((type) => gableType.includes(type))) ||
(gableEven.every((type) => type === LINE_TYPE.WALLLINE.EAVES) && gableOdd.every((type) => gableType.includes(type)))
) {
drawGabledRoof(this.id, this.canvas, textMode)
} else if (hasShed) {
const sheds = this.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.SHED)
const areLinesParallel = function (line1, line2) {
const angle1 = calculateAngle(line1.startPoint, line1.endPoint)
const angle2 = calculateAngle(line2.startPoint, line2.endPoint)
return (oddAllEaves && evenAllGable) || (evenAllEaves && oddAllGable)
}
const isShedRoof = function (types, lines) {
const gableTypes = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD]
if (!types.includes(LINE_TYPE.WALLLINE.SHED)) {
return false
}
const shedLines = lines.filter((line) => line.attributes?.type === LINE_TYPE.WALLLINE.SHED)
const areShedLinesParallel = function (shedLines) {
return shedLines.every((shed, i) => {
const nextShed = shedLines[(i + 1) % shedLines.length]
const angle1 = calculateAngle(shed.startPoint, shed.endPoint)
const angle2 = calculateAngle(nextShed.startPoint, nextShed.endPoint)
return angle1 === angle2
})
}
if (!areShedLinesParallel(shedLines)) {
return false
}
let isShedRoof = true
sheds.forEach((shed, i) => {
isShedRoof = areLinesParallel(shed, sheds[(i + 1) % sheds.length])
const getParallelEavesLines = function (shedLines, lines) {
const referenceAngle = calculateAngle(shedLines[0].startPoint, shedLines[0].endPoint)
const otherSideLines = lines.filter((line) => {
const lineAngle = calculateAngle(line.startPoint, line.endPoint)
return Math.abs(referenceAngle - lineAngle) === 180
})
if (isShedRoof) {
const eaves = this.lines
.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.EAVES)
.filter((line) => {
const angle1 = calculateAngle(sheds[0].startPoint, sheds[0].endPoint)
const angle2 = calculateAngle(line.startPoint, line.endPoint)
if (Math.abs(angle1 - angle2) === 180) {
return line
const containNotEaves = otherSideLines.filter((line) => line.attributes?.type !== LINE_TYPE.WALLLINE.EAVES)
if (containNotEaves.length === 0) {
return otherSideLines
} else {
return []
}
})
if (eaves.length > 0) {
const gables = this.lines.filter((line) => sheds.includes(line) === false && eaves.includes(line) === false)
const isGable = gables.every((line) => gableType.includes(line.attributes.type))
if (isGable) {
}
const parallelEaves = getParallelEavesLines(shedLines, lines)
if (parallelEaves.length === 0) {
return false
}
const remainingLines = lines.filter((line) => !shedLines.includes(line) && !parallelEaves.includes(line))
return remainingLines.every((line) => gableTypes.includes(line.attributes.type))
}
if (types.every((type) => type === LINE_TYPE.WALLLINE.EAVES)) {
// 용마루 -- straight-skeleton
// console.log('용마루 지붕')
drawSkeletonRidgeRoof(this.id, this.canvas, textMode)
} else if (isGableRoof(types)) {
// A형, B형 박공 지붕
// console.log('패턴 지붕')
drawGableRoof(this.id, this.canvas, textMode)
} else if (isShedRoof(types, this.lines)) {
// console.log('한쪽흐름 지붕')
drawShedRoof(this.id, this.canvas, textMode)
} else {
drawRidgeRoof(this.id, this.canvas, textMode)
}
} else {
drawRidgeRoof(this.id, this.canvas, textMode)
}
} else {
drawRidgeRoof(this.id, this.canvas, textMode)
}
} else {
drawRidgeRoof(this.id, this.canvas, textMode)
// console.log('변별로 설정')
drawRoofByAttribute(this.id, this.canvas, textMode)
}
},
@ -311,13 +400,31 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
const dy = Big(end.y).minus(Big(start.y))
const length = dx.pow(2).plus(dy.pow(2)).sqrt().times(10).round().toNumber()
const direction = getDirectionByPoint(start, end)
let left, top
if (direction === 'bottom') {
left = (start.x + end.x) / 2 - 50
top = (start.y + end.y) / 2
} else if (direction === 'top') {
left = (start.x + end.x) / 2 + 30
top = (start.y + end.y) / 2
} else if (direction === 'left') {
left = (start.x + end.x) / 2
top = (start.y + end.y) / 2 - 30
} else if (direction === 'right') {
left = (start.x + end.x) / 2
top = (start.y + end.y) / 2 + 30
}
let midPoint
midPoint = new fabric.Point((start.x + end.x) / 2, (start.y + end.y) / 2)
midPoint = new fabric.Point(left, top)
const degree = Big(Math.atan2(dy.toNumber(), dx.toNumber())).times(180).div(Math.PI).toNumber()
// Create new text object if it doesn't exist
// Create a new text object if it doesn't exist
const text = new fabric.Text(length.toString(), {
left: midPoint.x,
top: midPoint.y,
@ -366,7 +473,15 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
this.canvas = canvas
},
fillCellABType(
cell = { width: 50, height: 100, padding: 5, wallDirection: 'left', referenceDirection: 'none', startIndex: -1, isCellCenter: false },
cell = {
width: 50,
height: 100,
padding: 5,
wallDirection: 'left',
referenceDirection: 'none',
startIndex: -1,
isCellCenter: false,
},
) {
const points = this.points
@ -668,13 +783,115 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
return intersects % 2 === 1
},
inPolygonImproved(point) {
const vertices = this.getCurrentPoints()
let inside = false
const testX = Number(point.x.toFixed(this.toFixed))
const testY = Number(point.y.toFixed(this.toFixed))
for (let i = 0, j = vertices.length - 1; i < vertices.length; j = i++) {
const xi = Number(vertices[i].x.toFixed(this.toFixed))
const yi = Number(vertices[i].y.toFixed(this.toFixed))
const xj = Number(vertices[j].x.toFixed(this.toFixed))
const yj = Number(vertices[j].y.toFixed(this.toFixed))
// 점이 정점 위에 있는지 확인
if (Math.abs(xi - testX) <= 0.01 && Math.abs(yi - testY) <= 0.01) {
return true
}
// 점이 선분 위에 있는지 확인
if (this.isPointOnSegment(point, { x: xi, y: yi }, { x: xj, y: yj })) {
return true
}
// Ray casting 알고리즘 - 부동소수점 정밀도 개선
if (yi > testY !== yj > testY) {
const denominator = yj - yi
if (Math.abs(denominator) > 1e-10) {
// 0으로 나누기 방지
const intersection = ((xj - xi) * (testY - yi)) / denominator + xi
if (testX < intersection) {
inside = !inside
}
}
}
}
return inside
},
isPointOnSegment(point, segStart, segEnd) {
const tolerance = 0.1
const dxSegment = segEnd.x - segStart.x
const dySegment = segEnd.y - segStart.y
const dxPoint = point.x - segStart.x
const dyPoint = point.y - segStart.y
// 벡터의 외적을 계산하여 점이 선분 위에 있는지 확인
const crossProduct = Math.abs(dxPoint * dySegment - dyPoint * dxSegment)
if (crossProduct > tolerance) {
return false
}
// 점이 선분의 범위 내에 있는지 확인
const dotProduct = dxPoint * dxSegment + dyPoint * dySegment
const squaredLength = dxSegment * dxSegment + dySegment * dySegment
return dotProduct >= 0 && dotProduct <= squaredLength
},
setCoords: function () {
// 부모 클래스의 setCoords 호출
this.callSuper('setCoords')
// QPolygon의 경우 추가 처리 - 항상 강제로 재계산
if (this.canvas) {
// 모든 좌표 관련 캐시 초기화
delete this.oCoords
delete this.aCoords
delete this.__corner
// 다시 부모 setCoords 호출
this.callSuper('setCoords')
// 한 번 더 강제로 bounding rect 재계산
this._clearCache && this._clearCache()
}
},
containsPoint: function (point) {
// 먼저 좌표 업데이트
this.setCoords()
// viewport transform만 역변환 (캔버스 줌/팬 보정)
// 결과는 WORLD 좌표 (캔버스 좌표계)
let canvasPoint = point
if (this.canvas) {
const vpt = this.canvas.viewportTransform
if (vpt) {
const inverted = fabric.util.invertTransform(vpt)
canvasPoint = fabric.util.transformPoint(point, inverted)
}
}
// canvasPoint는 WORLD 좌표
// inPolygonImproved에서 getCurrentPoints()도 WORLD 좌표를 반환
// 따라서 좌표 시스템이 일치함
const checkPoint = {
x: Number(canvasPoint.x.toFixed(this.toFixed)),
y: Number(canvasPoint.y.toFixed(this.toFixed)),
}
if (this.name === POLYGON_TYPE.ROOF && this.isFixed) {
const isInside = this.inPolygon(point)
const isInside = this.inPolygonImproved(checkPoint)
if (!this.selectable) {
this.set('selectable', isInside)
}
return isInside
} else {
return this.callSuper('containsPoint', point)
return this.inPolygonImproved(checkPoint)
}
},

View File

@ -11,9 +11,9 @@ import { useCanvas } from '@/hooks/useCanvas'
import { usePlan } from '@/hooks/usePlan'
import { useContextMenu } from '@/hooks/useContextMenu'
import { useCanvasConfigInitialize } from '@/hooks/common/useCanvasConfigInitialize'
import { currentMenuState } from '@/store/canvasAtom'
import { canvasZoomState, currentMenuState } from '@/store/canvasAtom'
import { totalDisplaySelector } from '@/store/settingAtom'
import { MENU, POLYGON_TYPE } from '@/common/common'
import { POLYGON_TYPE } from '@/common/common'
import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider'
import { QcastContext } from '@/app/QcastProvider'
import {
@ -30,11 +30,15 @@ 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'
import { outerLinePointsState } from '@/store/outerLineAtom'
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()
@ -42,11 +46,13 @@ export default function CanvasFrame() {
const totalDisplay = useRecoilValue(totalDisplaySelector) //
const { setIsGlobalLoading } = useContext(QcastContext)
const resetModuleStatisticsState = useResetRecoilState(moduleStatisticsState)
const resetOuterLinePoints = useResetRecoilState(outerLinePointsState)
const resetMakersState = useResetRecoilState(makersState)
const resetSelectedMakerState = useResetRecoilState(selectedMakerState)
const resetSeriesState = useResetRecoilState(seriesState)
const resetModelsState = useResetRecoilState(modelsState)
const resetCompasDeg = useResetRecoilState(compasDegAtom)
const [zoom, setCanvasZoom] = useRecoilState(canvasZoomState)
const resetSelectedModelsState = useResetRecoilState(selectedModelsState)
const resetPcsCheckState = useResetRecoilState(pcsCheckState)
const { handleModuleSelectionTotal } = useCanvasPopupStatusController()
@ -64,15 +70,45 @@ export default function CanvasFrame() {
canvasLoadInit() //config
canvas?.renderAll() // .
if (canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE).length > 0) {
setSelectedMenu('module')
} else {
setSelectedMenu('surface')
if (canvas.viewportTransform) {
if (canvas.viewportTransform[0] !== 1) {
setCanvasZoom(Number((canvas.viewportTransform[0] * 100).toFixed(0)))
}
}
canvas.originViewPortTransform = canvas.viewportTransform
if (canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE).length > 0) {
setTimeout(() => {
setSelectedMenu('module')
}, 500)
} else if (canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL).length > 0) {
setSelectedMenu('outline')
} else {
setTimeout(() => {
setSelectedMenu('surface')
}, 500)
}
const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
roofs.forEach((roof) => {
const auxiliaryLines = canvas
.getObjects()
.filter((obj) => obj.name === 'auxiliaryLine' && roof.inPolygonImproved(obj.startPoint) && roof.inPolygonImproved(obj.endPoint))
auxiliaryLines.forEach((auxiliaryLine) => {
roof.innerLines.push(auxiliaryLine)
})
})
initEvent()
})
} else {
setSelectedMenu(null)
}
Object.keys(currentCanvasPlan).length > 0 && canvas && handleModuleSelectionTotal()
} else {
setSelectedMenu(null)
}
gridInit()
}
@ -92,6 +128,8 @@ export default function CanvasFrame() {
useEffect(() => {
setIsGlobalLoading(false)
// .
closeAll()
return () => {
canvas?.clear()
@ -101,6 +139,7 @@ export default function CanvasFrame() {
const resetRecoilData = () => {
// resetModuleStatisticsState()
resetOuterLinePoints()
resetMakersState()
resetSelectedMakerState()
resetSeriesState()
@ -110,6 +149,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,12 +31,13 @@ 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
key={`plan-${plan.id}`}
className={`canvas-page-box ${plan.isCurrent === true ? 'on' : ''}`}
onClick={() => handleCurrentPlan(plan.id)}
onClick={() => (plan.isCurrent ? '' : handleCurrentPlan(plan.id))}
>
<span>{`Plan ${plan.planNo}`}</span>
{plans.length > 1 && !pathname.includes('/estimate') && !pathname.includes('/simulator') && (

View File

@ -2,9 +2,9 @@
import { useContext, useEffect, useState } from 'react'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import { usePathname, useRouter } from 'next/navigation'
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'
import { v4 as uuidv4 } from 'uuid'
@ -25,17 +25,18 @@ import { useCommonUtils } from '@/hooks/common/useCommonUtils'
import useMenu from '@/hooks/common/useMenu'
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
import { useAxios } from '@/hooks/useAxios'
import { canvasSettingState, canvasState, canvasZoomState, currentMenuState, verticalHorizontalModeState } from '@/store/canvasAtom'
import {
canvasSettingState,
canvasState,
canvasZoomState,
currentCanvasPlanState,
currentMenuState,
verticalHorizontalModeState,
} from '@/store/canvasAtom'
import { sessionStore } from '@/store/commonAtom'
import { outerLinePointsState } from '@/store/outerLineAtom'
import { appMessageStore, globalLocaleStore } from '@/store/localeAtom'
import {
addedRoofsState,
basicSettingState,
corridorDimensionSelector,
selectedRoofMaterialSelector,
settingModalFirstOptionsState,
} from '@/store/settingAtom'
import { addedRoofsState, basicSettingState, selectedRoofMaterialSelector, settingModalFirstOptionsState } from '@/store/settingAtom'
import { placementShapeDrawingPointsState } from '@/store/placementShapeDrawingAtom'
import { commonUtilsState } from '@/store/commonUtilsAtom'
import { menusState } from '@/store/menuAtom'
@ -50,7 +51,10 @@ import JA from '@/locales/ja.json'
import { QcastContext } from '@/app/QcastProvider'
import { useRoofFn } from '@/hooks/common/useRoofFn'
import { usePolygon } from '@/hooks/usePolygon'
import { useTrestle } from '@/hooks/module/useTrestle'
export default function CanvasMenu(props) {
const [currentCanvasPlan, setCurrentCanvasPlan] = useRecoilState(currentCanvasPlanState)
const { selectedMenu, setSelectedMenu } = props
const pathname = usePathname()
const router = useRouter()
@ -67,6 +71,7 @@ export default function CanvasMenu(props) {
const globalLocale = useRecoilValue(globalLocaleStore)
const canvas = useRecoilValue(canvasState)
const { handleZoomClear, handleZoom } = useCanvasEvent()
const { setAllModuleSurfaceIsComplete, isAllComplete } = useTrestle()
const { handleMenu } = useMenu()
// const urlParams = useSearchParams()
const { handleEstimateSubmit, fetchSetting, estimateContextState, setEstimateContextState } = useEstimateController()
@ -96,7 +101,7 @@ export default function CanvasMenu(props) {
const [lockButtonStyle, setLockButtonStyle] = useState('') //
const setFloorPlanObjectNo = useSetRecoilState(floorPlanObjectState) //
const resetCommonUtils = useResetRecoilState(commonUtilsState)
//
const { objectNo, pid } = floorPlanState
@ -115,7 +120,7 @@ export default function CanvasMenu(props) {
const params = {
objectNo: objectNo,
planNo: selectedPlan.planNo,
planNo: selectedPlan?.planNo ? selectedPlan.planNo : pid,
schDownload: donwloadType,
schDrawingFlg: drawingFlg,
pwrGnrSimType: pwrGnrSimTypeRecoil.type,
@ -164,6 +169,7 @@ export default function CanvasMenu(props) {
}
const onClickNav = async (menu) => {
resetCommonUtils()
switch (menu.type) {
case 'drawing':
swalFire({
@ -192,8 +198,17 @@ export default function CanvasMenu(props) {
text: getMessage('module.delete.confirm'),
type: 'confirm',
confirmFn: () => {
const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
roofs.forEach((roof) => {
roof.set({
stroke: 'black',
strokeWidth: 3,
})
})
//
setAllModuleSurfaceIsComplete(false)
const moduleSurfacesArray = canvas
.getObjects()
.filter((obj) => [POLYGON_TYPE.MODULE_SETUP_SURFACE, POLYGON_TYPE.MODULE, POLYGON_TYPE.OBJECT_SURFACE].includes(obj.name))
@ -230,11 +245,18 @@ export default function CanvasMenu(props) {
router.push(`/floor-plan?pid=${pid}&objectNo=${objectNo}`)
setSelectedMenu('module')
}
await reloadCanvasStatus(objectNo, pid)
await reloadCanvasStatus(objectNo, currentCanvasPlan?.planNo ?? pid)
break
case 'estimate':
if (selectedMenu !== 'simulation') {
if (!isAllComplete()) {
swalFire({ text: getMessage('estimate.menu.move.valid1') })
return
}
}
setIsGlobalLoading(true)
promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan.planNo}/detail` }).then((res) => {
promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan?.planNo ?? pid}/detail` }).then((res) => {
if (res.status === 200) {
const estimateDetail = res.data
if (estimateDetail.estimateDate !== null) {
@ -242,7 +264,7 @@ export default function CanvasMenu(props) {
setCurrentMenu(menu.title)
setFloorPlanObjectNo({ floorPlanObjectNo: objectNo })
setIsGlobalLoading(false)
router.push(`/floor-plan/estimate/5?pid=${selectedPlan.planNo}&objectNo=${objectNo}`)
router.push(`/floor-plan/estimate/5?pid=${selectedPlan?.planNo ?? pid}&objectNo=${objectNo}`)
if (pathname === '/floor-plan/estimate/5') {
setIsGlobalLoading(false)
}
@ -255,13 +277,13 @@ export default function CanvasMenu(props) {
break
case 'simulation':
setIsGlobalLoading(true)
promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan.planNo}/detail` }).then((res) => {
promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan?.planNo ?? pid}/detail` }).then((res) => {
if (res.status === 200) {
const estimateDetail = res.data
if (estimateDetail.estimateDate !== null && estimateDetail.docNo) {
setSelectedMenu(menu.type)
setCurrentMenu(menu.title)
router.push(`/floor-plan/simulator/6?pid=${selectedPlan.planNo}&objectNo=${objectNo}`)
router.push(`/floor-plan/simulator/6?pid=${selectedPlan?.planNo ?? pid}&objectNo=${objectNo}`)
if (pathname === '/floor-plan/simulator/6') {
setIsGlobalLoading(false)
}
@ -303,7 +325,6 @@ export default function CanvasMenu(props) {
const settingsModalOptions = useRecoilState(settingModalFirstOptionsState)
useEffect(() => {
console.log(selectedMenu)
if (selectedMenu === 'placement') {
onClickPlacementInitialMenu()
}
@ -496,7 +517,10 @@ export default function CanvasMenu(props) {
if (createUser === 'T01' && sessionState.storeId !== 'T01') {
setAllButtonStyles('none')
} else {
setEstimateContextState({ tempFlg: estimateRecoilState.tempFlg, lockFlg: estimateRecoilState.lockFlg })
setEstimateContextState({
tempFlg: estimateRecoilState.tempFlg,
lockFlg: estimateRecoilState.lockFlg,
})
handleButtonStyles(estimateRecoilState.tempFlg, estimateRecoilState.lockFlg, estimateContextState.docNo)
}
}
@ -548,13 +572,26 @@ export default function CanvasMenu(props) {
{
<div className={`vertical-horizontal ${verticalHorizontalMode ? 'on' : ''}`}>
<span>{getMessage('plan.mode.vertical.horizontal')}</span>
<button onClick={() => setVerticalHorizontalMode(!verticalHorizontalMode)}>{verticalHorizontalMode ? 'ON' : 'OFF'}</button>
<button
title={`${getMessage('plan.mode.vertical.horizontal')} ${verticalHorizontalMode ? 'ON' : 'OFF'}`}
onClick={() => setVerticalHorizontalMode(!verticalHorizontalMode)}
>
{verticalHorizontalMode ? 'ON' : 'OFF'}
</button>
</div>
}
<div className="btn-from">
<button className={`btn01 ${commonUtils.text ? 'active' : ''}`} onClick={() => commonFunctions('text')}></button>
<button className={`btn02 ${commonUtils.dimension ? 'active' : ''} `} onClick={() => commonFunctions('dimension')}></button>
<button className={`btn03 ${commonUtils.distance ? 'active' : ''} `} onClick={() => commonFunctions('distance')}></button>
<button className={`btn01 ${commonUtils.text ? 'active' : ''}`} onClick={() => commonFunctions('text')} title="文字作成"></button>
<button
className={`btn02 ${commonUtils.dimension ? 'active' : ''} `}
onClick={() => commonFunctions('dimension')}
title="寸法作成"
></button>
<button
className={`btn03 ${commonUtils.distance ? 'active' : ''} `}
onClick={() => commonFunctions('distance')}
title="定規"
></button>
</div>
{isObjectNotEmpty(selectedRoofMaterial) && addedRoofs.length > 0 && (
<div className="select-box">
@ -580,6 +617,7 @@ export default function CanvasMenu(props) {
sourceKey={'index'}
targetKey={'index'}
disabled={+basicSetting.roofSizeSet === 3}
tagTitle={'屋根材変更'}
/>
}
</div>
@ -588,9 +626,10 @@ export default function CanvasMenu(props) {
<button
className={`btn10 ${floorPlanState.refFileModalOpen && 'active'}`}
onClick={() => setFloorPlanState({ ...floorPlanState, refFileModalOpen: true })}
title="読込"
></button>
{/*<button className="btn04" onClick={() => setShowCanvasSettingModal(true)}></button>*/}
<button className="btn04" onClick={handlePopup}></button>
<button className="btn04" onClick={handlePopup} title="設定"></button>
</div>
<div className="size-control">
<button
@ -599,7 +638,9 @@ export default function CanvasMenu(props) {
handleZoom(false)
}}
></button>
<span onClick={handleZoomClear}>{canvasZoom}%</span>
<span onClick={handleZoomClear} title="拡大・縮小">
{canvasZoom}%
</span>
<button
className="control-btn plus"
onClick={() => {
@ -608,8 +649,8 @@ export default function CanvasMenu(props) {
></button>
</div>
<div className="btn-from">
<button className="btn08" onClick={handleSaveCanvas}></button>
<button className="btn09" onClick={handleLeaveCanvas}></button>
<button className="btn08" onClick={handleSaveCanvas} title="保存"></button>
<button className="btn09" onClick={handleLeaveCanvas} title="物件検索画面へ移動"></button>
</div>
</>
)}
@ -634,7 +675,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

@ -1,15 +1,19 @@
'use client'
import { useEffect } from 'react'
import { useContext, useEffect } from 'react'
import CanvasMenu from '@/components/floor-plan/CanvasMenu'
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
import { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
import { usePopup } from '@/hooks/usePopup'
import '@/styles/contents.scss'
import { notFound, useSearchParams } from 'next/navigation'
import { useRecoilState, useResetRecoilState } from 'recoil'
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
import { correntObjectNoState } from '@/store/settingAtom'
import { currentMenuState } from '@/store/canvasAtom'
import { globalLocaleStore } from '@/store/localeAtom'
import { useAxios } from '@/hooks/useAxios'
import { GlobalDataContext } from '@/app/GlobalDataProvider'
import { sessionStore } from '@/store/commonAtom'
export default function FloorPlan({ children }) {
const [correntObjectNo, setCurrentObjectNo] = useRecoilState(correntObjectNoState)
@ -20,12 +24,46 @@ export default function FloorPlan({ children }) {
const { selectedMenu, setSelectedMenu } = useCanvasMenu()
const { fetchSettings } = useCanvasSetting()
const resetCurrentMenu = useResetRecoilState(currentMenuState)
const globalLocaleState = useRecoilValue(globalLocaleStore)
const { promiseGet } = useAxios(globalLocaleState)
const { setManagementState } = useContext(GlobalDataContext)
const [sessionState, setSessionState] = useRecoilState(sessionStore)
useEffect(() => {
getStuffDetailInfo()
return () => {
resetCurrentMenu()
}
}, [])
const getStuffDetailInfo = () => {
promiseGet({ url: `/api/object/${objectNo}/detail` }).then((res) => {
if (res.status === 200) {
const { data } = res
console.log(data)
let surfaceTypeValue
if (res.data.surfaceType === 'Ⅲ・Ⅳ') {
surfaceTypeValue = '3'
} else if (res.data.surfaceType === 'Ⅱ') {
surfaceTypeValue = '2'
}
// 0
if (res.data.installHeight === '0') {
res.data.installHeight = ''
}
setSessionState((prev) => ({
...prev,
oneTwoStoreId: res.data.saleStoreId
}));
setManagementState({ ...res.data, surfaceTypeValue: surfaceTypeValue })
}
})
}
/**
* URL 파라미터에서 objectNo 설정
*/

View File

@ -5,9 +5,10 @@ import { useEffect } from 'react'
import { useMessage } from '@/hooks/useMessage'
import useMenu from '@/hooks/common/useMenu'
import { canvasState, currentMenuState } from '@/store/canvasAtom'
import { useRecoilState, useRecoilValue } from 'recoil'
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
import { subMenusState } from '@/store/menuAtom'
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
import { commonUtilsState } from '@/store/commonUtilsAtom'
export default function MenuDepth01() {
const canvas = useRecoilValue(canvasState)
@ -16,8 +17,10 @@ export default function MenuDepth01() {
const { selectedMenu, setSelectedMenu } = useCanvasMenu()
const [currentMenu, setCurrentMenu] = useRecoilState(currentMenuState)
const subMenus = useRecoilValue(subMenusState)
const resetCommonUtils = useResetRecoilState(commonUtilsState)
const onClickMenu = ({ id, menu }) => {
resetCommonUtils()
if (menu === currentMenu) {
handleMenu(selectedMenu)
} else {

View File

@ -120,7 +120,7 @@ export default function ImgLoad() {
value={refImage ? (refImage?.name ?? '') : (currentCanvasPlan?.bgImageName ?? '')}
readOnly
/>
{refImage && <button className="img-check" onClick={handleFileDelete}></button>}
{currentCanvasPlan?.bgImageName && <button className="img-check" onClick={handleFileDelete}></button>}
</div>
</div>
</div>

View File

@ -4,6 +4,7 @@ import { globalPitchState, pitchSelector, pitchTextSelector } from '@/store/canv
import { useRecoilState } from 'recoil'
import { useRef } from 'react'
import { usePopup } from '@/hooks/usePopup'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function Slope({ id, pos = { x: 50, y: 230 } }) {
const { getMessage } = useMessage()
@ -22,7 +23,19 @@ export default function Slope({ id, pos = { x: 50, y: 230 } }) {
{getMessage('slope')}
</span>
<div className="input-grid mr5">
<input type="text" className="input-origin block" defaultValue={globalPitch} ref={inputRef} />
{/*<input type="text" className="input-origin block" defaultValue={globalPitch} ref={inputRef} />*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
ref={inputRef}
value={globalPitch}
options={{
allowNegative: false,
allowDecimal: true //(index !== 0),
}}
></CalculatorInput>
</div>
<span className="thin">{pitchText}</span>
</div>

View File

@ -8,8 +8,12 @@ import { OUTER_LINE_TYPE } from '@/store/outerLineAtom'
import OuterLineWall from '@/components/floor-plan/modal/lineTypes/OuterLineWall'
import { useAuxiliaryDrawing } from '@/hooks/roofcover/useAuxiliaryDrawing'
import { usePopup } from '@/hooks/usePopup'
import { useEffect } from 'react'
import { useRecoilValue } from 'recoil'
import { canvasState } from '@/store/canvasAtom'
export default function AuxiliaryDrawing({ id, pos = { x: 50, y: 230 } }) {
const canvas = useRecoilValue(canvasState)
const { getMessage } = useMessage()
const { closePopup } = usePopup()
@ -52,6 +56,15 @@ export default function AuxiliaryDrawing({ id, pos = { x: 50, y: 230 } }) {
cutAuxiliary,
} = useAuxiliaryDrawing(id)
useEffect(() => {
return () => {
const auxiliaryLines = canvas.getObjects().filter((line) => line.name === 'auxiliaryLine' && !line.isAuxiliaryFixed) // .
if (auxiliaryLines.length > 0) {
handleFix()
}
}
}, [])
const outerLineProps = {
length1,
setLength1,

View File

@ -7,6 +7,8 @@ import { useState } from 'react'
import { currentObjectState } from '@/store/canvasAtom'
import { useAuxiliaryDrawing } from '@/hooks/roofcover/useAuxiliaryDrawing'
import { useSwal } from '@/hooks/useSwal'
import { normalizeDigits } from '@/util/input-utils'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function AuxiliaryEdit(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
@ -40,15 +42,15 @@ export default function AuxiliaryEdit(props) {
if (currentObject) {
copy(
currentObject,
arrow2 ? (arrow2 === '←' ? Number(horizonSize) * -1 : Number(horizonSize)) : 0,
arrow1 ? (arrow1 === '↑' ? Number(verticalSize) * -1 : Number(verticalSize)) : 0,
arrow2 ? (arrow2 === '←' ? (Number(normalizeDigits(horizonSize)) / 10) * -1 : Number(normalizeDigits(horizonSize)) / 10) : 0,
arrow1 ? (arrow1 === '↑' ? (Number(normalizeDigits(verticalSize)) / 10) * -1 : Number(normalizeDigits(verticalSize)) / 10) : 0,
)
}
} else {
move(
currentObject,
arrow2 ? (arrow2 === '←' ? Number(horizonSize) * -1 : Number(horizonSize)) : 0,
arrow1 ? (arrow1 === '↑' ? Number(verticalSize) * -1 : Number(verticalSize)) : 0,
arrow2 ? (arrow2 === '←' ? (Number(normalizeDigits(horizonSize)) / 10) * -1 : Number(normalizeDigits(horizonSize)) / 10) : 0,
arrow1 ? (arrow1 === '↑' ? (Number(normalizeDigits(verticalSize)) / 10) * -1 : Number(normalizeDigits(verticalSize)) / 10) : 0,
)
}
@ -65,7 +67,19 @@ export default function AuxiliaryEdit(props) {
<p className="mb5">{getMessage('length')}</p>
<div className="input-move-wrap mb5">
<div className="input-move">
<input type="text" className="input-origin" value={verticalSize} onChange={(e) => setVerticalSize(e.target.value)} />
{/*<input type="text" className="input-origin" value={verticalSize} onChange={(e) => setVerticalSize(normalizeDigits(e.target.value))} />*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={verticalSize}
onChange={(value) => setVerticalSize(value)}
options={{
allowNegative: false,
allowDecimal: false
}}
/>
</div>
<span>mm</span>
<div className="direction-move-wrap">
@ -87,7 +101,19 @@ export default function AuxiliaryEdit(props) {
</div>
<div className="input-move-wrap">
<div className="input-move">
<input type="text" className="input-origin" value={horizonSize} onChange={(e) => setHorizonSize(e.target.value)} />
{/*<input type="text" className="input-origin" value={horizonSize} onChange={(e) => setHorizonSize(normalizeDigits(e.target.value))} />*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={horizonSize}
onChange={(value) => setHorizonSize(value)}
options={{
allowNegative: false,
allowDecimal: false
}}
/>
</div>
<span>mm</span>
<div className="direction-move-wrap">

View File

@ -7,19 +7,22 @@ import { canvasState, currentObjectState } from '@/store/canvasAtom'
import { useEffect, useState } from 'react'
import Big from 'big.js'
import { calcLineActualSize, calcLinePlaneSize } from '@/util/qpolygon-utils'
import { normalizeDigits } from '@/util/input-utils'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function AuxiliarySize(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
const { id, pos = contextPopupPosition } = props
const [checkedRadio, setCheckedRadio] = useState(null)
const [value1, setValue1] = useState(null)
const [value2, setValue2] = useState(null)
const [value1, setValue1] = useState('')
const [value2, setValue2] = useState('')
const [size, setSize] = useState(0)
const { getMessage } = useMessage()
const { closePopup } = usePopup()
const currentObject = useRecoilValue(currentObjectState)
const canvas = useRecoilValue(canvasState)
useEffect(() => {
return () => {
canvas?.discardActiveObject()
@ -36,13 +39,14 @@ export default function AuxiliarySize(props) {
}, [currentObject])
const handleInput = (e) => {
let value = e.target.value.replace(/^0+/, '')
let value = e.replace(/^0+/, '')
if (value === '') {
if (checkedRadio === 1) setValue1(value)
if (checkedRadio === 2) setValue2(value)
setSize(0)
} else {
value = Big(value.replace(/[^0-9]/g, ''))
//value = Big(value.replace(/[^0-9]/g, ''))
value = Big(normalizeDigits(value))
if (checkedRadio === 1) setValue1(value.toNumber())
if (checkedRadio === 2) setValue2(value.toNumber())
setSize(value.div(10).toNumber())
@ -128,7 +132,20 @@ export default function AuxiliarySize(props) {
<div className="outline-form">
<span style={{ width: 'auto' }}>{getMessage('length')}</span>
<div className="input-grid mr5">
<input type="text" className="input-origin block" value={value1} readOnly={checkedRadio !== 1} onChange={handleInput} />
{/*<input type="text" className="input-origin block" value={value1} readOnly={checkedRadio !== 1} onChange={handleInput} />*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={value1}
onChange={handleInput}
readOnly={checkedRadio !== 1}
options={{
allowNegative: false,
allowDecimal: false
}}
/>
</div>
<span className="thin">mm</span>
</div>
@ -147,7 +164,20 @@ export default function AuxiliarySize(props) {
<div className="outline-form">
<span style={{ width: 'auto' }}>{getMessage('length')}</span>
<div className="input-grid mr5">
<input type="text" className="input-origin block" value={value2} readOnly={checkedRadio !== 2} onChange={handleInput} />
{/*<input type="text" className="input-origin block" value={value2} readOnly={checkedRadio !== 2} onChange={handleInput} />*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={value2}
onChange={handleInput}
readOnly={checkedRadio !== 2}
options={{
allowNegative: false,
allowDecimal: false
}}
/>
</div>
<span className="thin">mm</span>
</div>

View File

@ -1,109 +1,89 @@
import { useMessage } from '@/hooks/useMessage'
import { MODULE_SETUP_TYPE, POLYGON_TYPE } from '@/common/common'
import WithDraggable from '@/components/common/draggable/WithDraggable'
import { useContext, useEffect, useRef, useState } from 'react'
import Module from '@/components/floor-plan/modal/basic/step/Module'
import PitchModule from '@/components/floor-plan/modal/basic/step/pitch/PitchModule'
import { Orientation } from '@/components/floor-plan/modal/basic/step/Orientation'
import PitchPlacement from '@/components/floor-plan/modal/basic/step/pitch/PitchPlacement'
import Placement from '@/components/floor-plan/modal/basic/step/Placement'
import { useRecoilValue, useRecoilState } from 'recoil'
import { canvasSettingState, canvasState, checkedModuleState, isManualModuleSetupState } from '@/store/canvasAtom'
import { usePopup } from '@/hooks/usePopup'
import { Orientation } from '@/components/floor-plan/modal/basic/step/Orientation'
import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting'
import { useEvent } from '@/hooks/useEvent'
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
import { addedRoofsState, corridorDimensionSelector, basicSettingState } from '@/store/settingAtom'
import { isObjectNotEmpty } from '@/util/common-utils'
import Swal from 'sweetalert2'
import { useCanvasPopupStatusController } from '@/hooks/common/useCanvasPopupStatusController'
import { useMasterController } from '@/hooks/common/useMasterController'
import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting'
import { useModuleSelection } from '@/hooks/module/useModuleSelection'
import { useOrientation } from '@/hooks/module/useOrientation'
import { useMessage } from '@/hooks/useMessage'
import { usePopup } from '@/hooks/usePopup'
import {
canvasState,
checkedModuleState,
currentCanvasPlanState,
isManualModuleLayoutSetupState,
isManualModuleSetupState,
toggleManualSetupModeState,
} from '@/store/canvasAtom'
import { loginUserStore } from '@/store/commonAtom'
import { currentCanvasPlanState } from '@/store/canvasAtom'
import { POLYGON_TYPE } from '@/common/common'
import { roofsState } from '@/store/roofAtom'
import { moduleSelectionDataState } from '@/store/selectedModuleOptions'
import { addedRoofsState, basicSettingState } from '@/store/settingAtom'
import { isObjectNotEmpty } from '@/util/common-utils'
import { useEffect, useRef, useState } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import Swal from 'sweetalert2'
import Trestle from './step/Trestle'
export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
const { getMessage } = useMessage()
const { closePopup } = usePopup()
const [tabNum, setTabNum] = useState(1)
const canvasSetting = useRecoilValue(canvasSettingState)
const orientationRef = useRef(null)
const { initEvent } = useEvent()
const [isManualModuleSetup, setIsManualModuleSetup] = useRecoilState(isManualModuleSetupState)
const moduleSelectionData = useRecoilValue(moduleSelectionDataState)
const addedRoofs = useRecoilValue(addedRoofsState)
const [isManualModuleLayoutSetup, setIsManualModuleLayoutSetup] = useRecoilState(isManualModuleLayoutSetupState)
const trestleRef = useRef(null)
const [moduleSelectionData, setModuleSelectionData] = useRecoilState(moduleSelectionDataState)
const [addedRoofs, setAddedRoofs] = useRecoilState(addedRoofsState)
const loginUserState = useRecoilValue(loginUserStore)
const currentCanvasPlan = useRecoilValue(currentCanvasPlanState)
const canvas = useRecoilValue(canvasState)
const [basicSetting, setBasicSettings] = useRecoilState(basicSettingState)
const [isClosePopup, setIsClosePopup] = useState({ close: false, id: 0 })
const [checkedModules, setCheckedModules] = useRecoilState(checkedModuleState)
const [roofs, setRoofs] = useState(addedRoofs)
const [manualSetupMode, setManualSetupMode] = useRecoilState(toggleManualSetupModeState)
const [layoutSetup, setLayoutSetup] = useState([{}])
const {
selectedModules,
roughnessCodes,
windSpeedCodes,
managementState,
setManagementState,
moduleList,
setSelectedModules,
selectedSurfaceType,
setSelectedSurfaceType,
installHeight,
setInstallHeight,
standardWindSpeed,
setStandardWindSpeed,
verticalSnowCover,
setVerticalSnowCover,
handleChangeModule,
handleChangeSurfaceType,
handleChangeWindSpeed,
handleChangeInstallHeight,
handleChangeVerticalSnowCover,
} = useModuleSelection({ addedRoofs })
const { nextStep, compasDeg, setCompasDeg } = useOrientation()
const { trigger: orientationTrigger } = useCanvasPopupStatusController(1)
const { trigger: trestleTrigger } = useCanvasPopupStatusController(2)
const { trigger: placementTrigger } = useCanvasPopupStatusController(3)
const [roofsStore, setRoofsStore] = useRecoilState(roofsState)
const [isFold, setIsFold] = useState(false)
// const { initEvent } = useContext(EventContext)
const { manualModuleSetup, autoModuleSetup, manualFlatroofModuleSetup, autoFlatroofModuleSetup } = useModuleBasicSetting(tabNum)
const { manualModuleSetup, autoModuleSetup, manualFlatroofModuleSetup, autoFlatroofModuleSetup, manualModuleLayoutSetup, restoreModuleInstArea } =
useModuleBasicSetting(tabNum)
const { updateObjectDate } = useMasterController()
const handleBtnNextStep = () => {
if (tabNum === 1) {
orientationRef.current.handleNextStep()
} else if (tabNum === 2) {
if (basicSetting.roofSizeSet !== '3') {
if (!isObjectNotEmpty(moduleSelectionData.module)) {
Swal.fire({
title: getMessage('module.not.found'),
icon: 'warning',
})
return
}
if (addedRoofs.length !== moduleSelectionData.roofConstructions.length) {
Swal.fire({
title: getMessage('construction.length.difference'),
icon: 'warning',
})
return
}
//
updateObjectDataApi({
objectNo: currentCanvasPlan.objectNo, //_no
standardWindSpeedId: moduleSelectionData.common.stdWindSpeed, //
verticalSnowCover: moduleSelectionData.common.stdSnowLd, //
surfaceType: moduleSelectionData.common.illuminationTpNm, //
installHeight: moduleSelectionData.common.instHt, //
userId: loginUserState.userId, //
})
} else {
if (!isObjectNotEmpty(moduleSelectionData.module)) {
Swal.fire({
title: getMessage('module.not.found'),
icon: 'warning',
})
return
}
}
}
setTabNum(tabNum + 1)
}
const placementRef = {
isChidori: useRef('false'),
setupLocation: useRef('eaves'),
isMaxSetup: useRef('false'),
}
const placementFlatRef = {
setupLocation: useRef('south'),
}
const handleManualModuleSetup = () => {
setIsManualModuleSetup(!isManualModuleSetup)
}
const updateObjectDataApi = async (params) => {
const res = await updateObjectDate(params)
}
useEffect(() => {
const moduleTabNum = basicSetting.roofSizeSet != 3 ? 3 : 2
let hasModules = canvas
.getObjects()
.filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE)
@ -111,23 +91,42 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
if (hasModules) {
orientationRef.current.handleNextStep()
setTabNum(3)
setTabNum(moduleTabNum)
}
}, [])
//
const handleClosePopup = (id) => {
if (tabNum == 3) {
if (isManualModuleSetup) {
setIsManualModuleSetup(false)
useEffect(() => {
if (roofsStore && addedRoofs) {
setRoofs(
addedRoofs.map((roof, index) => {
return {
...roof,
...roofsStore[index]?.addRoof,
construction: roofsStore[index]?.construction,
trestle: roofsStore[index]?.trestle,
trestleDetail: roofsStore[index]?.trestleDetail,
}
}),
)
setModuleSelectionData({
...moduleSelectionData,
roofConstructions: roofsStore.map((roof) => {
return {
roofIndex: roof.roofIndex,
addRoof: roof.addRoof,
construction: roof.construction,
trestle: roof.trestle,
trestleDetail: roof.trestleDetail,
}
setIsClosePopup({ close: true, id: id })
}),
})
}
}, [roofsStore, addedRoofs])
useEffect(() => {
if (basicSetting.roofSizeSet !== '3') {
manualModuleSetup(placementRef)
manualModuleSetup()
} else {
manualFlatroofModuleSetup(placementFlatRef)
}
@ -140,55 +139,231 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
setIsManualModuleSetup(false)
}, [checkedModules])
useEffect(() => {
if (basicSetting.roofSizeSet !== '3') {
if (manualSetupMode.indexOf('manualSetup') > -1) {
manualModuleSetup()
} else if (manualSetupMode.indexOf('manualLayoutSetup') > -1) {
manualModuleLayoutSetup(layoutSetup)
} else if (manualSetupMode.indexOf('off') > -1) {
manualModuleSetup()
manualModuleLayoutSetup(layoutSetup)
}
} else {
manualFlatroofModuleSetup(placementFlatRef)
}
if (isClosePopup.close) {
closePopup(isClosePopup.id)
}
}, [manualSetupMode, isClosePopup])
useEffect(() => {
if (isManualModuleLayoutSetup) {
manualModuleLayoutSetup(layoutSetup)
}
}, [layoutSetup])
useEffect(() => {
setIsManualModuleSetup(false)
setIsManualModuleLayoutSetup(false)
setManualSetupMode(`off`)
}, [checkedModules])
const handleBtnNextStep = () => {
if (tabNum === 1) {
orientationRef.current.handleNextStep()
setAddedRoofs(roofs)
// setTabNum(tabNum + 1)
return
} else if (tabNum === 2) {
if (basicSetting.roofSizeSet !== '3') {
// if (addedRoofs.length !== moduleSelectionData.roofConstructions.length) {
// Swal.fire({
// title: getMessage('construction.length.difference'),
// icon: 'warning',
// })
// return
// }
trestleRef.current.isComplete().then((res) => {
if (!res) return
})
//
} else {
if (!isObjectNotEmpty(moduleSelectionData.module)) {
Swal.fire({
title: getMessage('module.not.found'),
icon: 'warning',
})
return
}
setTabNum(tabNum + 1)
}
}
}
const placementFlatRef = {
setupLocation: useRef('south'),
}
const handleManualModuleSetup = () => {
setManualSetupMode(`manualSetup_${!isManualModuleSetup}`)
setIsManualModuleSetup(!isManualModuleSetup)
}
const handleManualModuleLayoutSetup = () => {
setManualSetupMode(`manualLayoutSetup_${!isManualModuleLayoutSetup}`)
setIsManualModuleLayoutSetup(!isManualModuleLayoutSetup)
}
const updateObjectDataApi = async (params) => {
const res = await updateObjectDate(params)
}
//
const handleClosePopup = (id) => {
if (tabNum == 3) {
if (isManualModuleSetup) {
setIsManualModuleSetup(false)
}
if (isManualModuleLayoutSetup) {
setIsManualModuleLayoutSetup(false)
}
}
setIsClosePopup({ close: true, id: id })
}
const orientationProps = {
roofs,
setRoofs,
tabNum,
setTabNum,
compasDeg, //
setCompasDeg,
selectedModules,
moduleSelectionData,
setModuleSelectionData,
roughnessCodes, //
windSpeedCodes, //
managementState,
setManagementState,
moduleList, //
setSelectedModules,
selectedSurfaceType,
setSelectedSurfaceType,
installHeight, //
setInstallHeight,
standardWindSpeed, //
setStandardWindSpeed,
verticalSnowCover, //
setVerticalSnowCover,
currentCanvasPlan,
loginUserState,
handleChangeModule,
handleChangeSurfaceType,
handleChangeWindSpeed,
handleChangeInstallHeight,
handleChangeVerticalSnowCover,
orientationTrigger,
nextStep,
updateObjectDataApi,
}
const trestleProps = {
roofs,
setRoofs,
setRoofsStore,
tabNum,
setTabNum,
moduleSelectionData,
setModuleSelectionData,
trestleTrigger,
}
const placementProps = {}
return (
<WithDraggable isShow={true} pos={pos} className="lx-2">
<WithDraggable.Header title={getMessage('plan.menu.module.circuit.setting.default')} onClose={() => handleClosePopup(id)} />
<WithDraggable isShow={true} pos={pos} className={basicSetting.roofSizeSet && basicSetting.roofSizeSet != '3' ? 'll' : 'lx-2'}>
<WithDraggable.Header
title={getMessage('plan.menu.module.circuit.setting.default')}
isFold={isFold}
onClose={() => handleClosePopup(id)}
onFold={() => setIsFold(!isFold)}
/>
<WithDraggable.Body>
<div style={{ display: isFold ? 'none' : 'block' }}>
<div className="roof-module-tab">
<div className={`module-tab-bx act`}>{getMessage('modal.module.basic.setting.orientation.setting')}</div>
<span className={`tab-arr ${tabNum !== 1 ? 'act' : ''}`}></span>
{basicSetting.roofSizeSet && basicSetting.roofSizeSet != '3' && (
<>
<div className={`module-tab-bx ${tabNum !== 1 ? 'act' : ''}`}>{getMessage('modal.module.basic.setting.module.setting')}</div>
<span className={`tab-arr ${tabNum === 3 ? 'act' : ''}`}></span>
<div className={`module-tab-bx ${tabNum === 3 ? 'act' : ''}`}>{getMessage('modal.module.basic.setting.module.placement')}</div>
</>
)}
{basicSetting.roofSizeSet && basicSetting.roofSizeSet == '3' && (
<>
<div className={`module-tab-bx ${tabNum === 2 ? 'act' : ''}`}>{getMessage('modal.module.basic.setting.module.placement')}</div>
</>
)}
</div>
{tabNum === 1 && <Orientation ref={orientationRef} tabNum={tabNum} setTabNum={setTabNum} />}
{tabNum === 1 && <Orientation ref={orientationRef} {...orientationProps} />}
{/*배치면 초기설정 - 입력방법: 복시도 입력 || 실측값 입력*/}
{basicSetting.roofSizeSet && basicSetting.roofSizeSet != '3' && tabNum === 2 && <Module setTabNum={setTabNum} />}
{basicSetting.roofSizeSet && basicSetting.roofSizeSet != '3' && tabNum === 3 && <Placement setTabNum={setTabNum} ref={placementRef} />}
{basicSetting.roofSizeSet && basicSetting.roofSizeSet != '3' && tabNum === 2 && <Trestle ref={trestleRef} {...trestleProps} />}
{basicSetting.roofSizeSet && basicSetting.roofSizeSet != '3' && tabNum === 3 && (
<Placement setTabNum={setTabNum} layoutSetup={layoutSetup} setLayoutSetup={setLayoutSetup} />
)}
{/*배치면 초기설정 - 입력방법: 육지붕*/}
{basicSetting.roofSizeSet && basicSetting.roofSizeSet == '3' && tabNum === 2 && <PitchModule setTabNum={setTabNum} />}
{basicSetting.roofSizeSet && basicSetting.roofSizeSet == '3' && tabNum === 3 && (
{/* {basicSetting.roofSizeSet && basicSetting.roofSizeSet == '3' && tabNum === 3 && <PitchModule setTabNum={setTabNum} />} */}
{basicSetting.roofSizeSet && basicSetting.roofSizeSet == '3' && tabNum === 2 && (
<PitchPlacement setTabNum={setTabNum} ref={placementFlatRef} />
)}
</div>
<div className="grid-btn-wrap">
{/* {tabNum === 1 && <button className="btn-frame modal mr5">{getMessage('modal.common.save')}</button>} */}
{basicSetting.roofSizeSet && basicSetting.roofSizeSet != '3' && (
<>
{tabNum !== 1 && (
<button className="btn-frame modal mr5" onClick={() => setTabNum(tabNum - 1)}>
{getMessage('modal.module.basic.setting.prev')}
</button>
)}
{/*{tabNum !== 3 && <button className="btn-frame modal act mr5">{getMessage('modal.common.save')}</button>}*/}
{tabNum !== 3 && (
<button className="btn-frame modal" onClick={handleBtnNextStep}>
Next
</button>
)}
{tabNum === 3 && (
<>
{basicSetting.roofSizeSet && basicSetting.roofSizeSet != '3' && (
<>
<button className={`btn-frame modal mr5 ${isManualModuleLayoutSetup ? 'act' : ''}`} onClick={handleManualModuleLayoutSetup}>
{getMessage('modal.module.basic.setting.row.batch')}
</button>
<button className="btn-frame modal mr5" onClick={() => autoModuleSetup(MODULE_SETUP_TYPE.LAYOUT, layoutSetup)}>
{getMessage('modal.module.basic.setting.auto.row.batch')}
</button>
<button className={`btn-frame modal mr5 ${isManualModuleSetup ? 'act' : ''}`} onClick={handleManualModuleSetup}>
{getMessage('modal.module.basic.setting.passivity.placement')}
</button>
<button className="btn-frame modal act" onClick={() => autoModuleSetup(placementRef)}>
<button className="btn-frame modal act mr5" onClick={() => autoModuleSetup(MODULE_SETUP_TYPE.AUTO)}>
{getMessage('modal.module.basic.setting.auto.placement')}
</button>
</>
)}
</>
)}
{basicSetting.roofSizeSet && basicSetting.roofSizeSet == '3' && (
<>
{tabNum === 1 && (
<button className="btn-frame modal" onClick={handleBtnNextStep}>
Next
</button>
)}
{tabNum === 2 && (
<>
<button className="btn-frame modal mr5" onClick={() => setTabNum(tabNum - 1)}>
{getMessage('modal.module.basic.setting.prev')}
</button>
<button className={`btn-frame modal mr5 ${isManualModuleSetup ? 'act' : ''}`} onClick={handleManualModuleSetup}>
{getMessage('modal.module.basic.setting.passivity.placement')}
</button>

View File

@ -10,6 +10,8 @@ import { useDebounceValue } from 'usehooks-ts'
import { moduleSelectionDataState } from '@/store/selectedModuleOptions'
import { useCanvasPopupStatusController } from '@/hooks/common/useCanvasPopupStatusController'
import { isObjectNotEmpty } from '@/util/common-utils'
import { normalizeDecimal} from '@/util/input-utils'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function Module({ setTabNum }) {
const { getMessage } = useMessage()
@ -184,11 +186,23 @@ export default function Module({ setTabNum }) {
<div className="eaves-keraba-td">
<div className="outline-form">
<div className="grid-select mr10">
<input
type="text"
{/*<input*/}
{/* type="text"*/}
{/* className="input-origin block"*/}
{/* value={inputInstallHeight}*/}
{/* onChange={(e) => setInputInstallHeight(normalizeDecimal(e.target.value))}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={inputInstallHeight}
onChange={(e) => setInputInstallHeight(e.target.value)}
onChange={(value) => setInputInstallHeight(value)}
options={{
allowNegative: false,
allowDecimal: false
}}
/>
</div>
<span className="thin">m</span>
@ -221,11 +235,23 @@ export default function Module({ setTabNum }) {
<div className="eaves-keraba-td">
<div className="outline-form">
<div className="grid-select mr10">
<input
type="text"
{/*<input*/}
{/* type="text"*/}
{/* className="input-origin block"*/}
{/* value={inputVerticalSnowCover}*/}
{/* onChange={(e) => setInputVerticalSnowCover(normalizeDecimal(e.target.value))}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={inputVerticalSnowCover}
onChange={(e) => setInputVerticalSnowCover(e.target.value)}
onChange={(value) => setInputVerticalSnowCover(value)}
options={{
allowNegative: false,
allowDecimal: false
}}
/>
</div>
<span className="thin">cm</span>

View File

@ -1,54 +1,399 @@
import { forwardRef, useContext, useEffect, useImperativeHandle, useState } from 'react'
import { forwardRef, use, useContext, useEffect, useImperativeHandle, useState } from 'react'
import { useMessage } from '@/hooks/useMessage'
import { useOrientation } from '@/hooks/module/useOrientation'
import { getDegreeInOrientation } from '@/util/canvas-util'
import { numberCheck } from '@/util/common-utils'
import { useCanvasPopupStatusController } from '@/hooks/common/useCanvasPopupStatusController'
import { addedRoofsState, basicSettingState } from '@/store/settingAtom'
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
import QSelectBox from '@/components/common/select/QSelectBox'
import { roofsState } from '@/store/roofAtom'
import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting'
import { useCommonCode } from '@/hooks/common/useCommonCode'
import Swal from 'sweetalert2'
import { normalizeDecimal} from '@/util/input-utils'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export const Orientation = forwardRef(({ tabNum }, ref) => {
export const Orientation = forwardRef((props, ref) => {
const { getMessage } = useMessage()
const { trigger: canvasPopupStatusTrigger } = useCanvasPopupStatusController(1)
const { nextStep, compasDeg, setCompasDeg } = useOrientation()
const { findCommonCode } = useCommonCode()
const [hasAnglePassivity, setHasAnglePassivity] = useState(false)
const basicSetting = useRecoilValue(basicSettingState)
const [addedRoofs, setAddedRoofs] = useRecoilState(addedRoofsState) //
const [roofsStore, setRoofsStore] = useRecoilState(roofsState)
const [roofTab, setRoofTab] = useState(0) //
const [selectedModuleSeries, setSelectedModuleSeries] = useState(null)
const [moduleSeriesList, setModuleSeriesList] = useState([])
const [filteredModuleList, setFilteredModuleList] = useState([])
const {
roofs,
setRoofs,
tabNum,
setTabNum,
compasDeg,
setCompasDeg,
selectedModules,
roughnessCodes,
windSpeedCodes,
managementState,
setManagementState,
moduleList,
moduleSelectionData,
setModuleSelectionData,
setSelectedModules,
selectedSurfaceType,
setSelectedSurfaceType,
installHeight,
setInstallHeight,
standardWindSpeed,
setStandardWindSpeed,
verticalSnowCover,
setVerticalSnowCover,
orientationTrigger,
nextStep,
currentCanvasPlan,
loginUserState,
updateObjectDataApi,
} = props
const [inputCompasDeg, setInputCompasDeg] = useState(compasDeg ?? 0)
const [inputInstallHeight, setInputInstallHeight] = useState('0')
const [inputMargin, setInputMargin] = useState('0')
const [inputVerticalSnowCover, setInputVerticalSnowCover] = useState('0')
const [inputRoughness, setInputRoughness] = useState(selectedSurfaceType)
const [inputStandardWindSpeed, setInputStandardWindSpeed] = useState(standardWindSpeed)
const { restoreModuleInstArea } = useModuleBasicSetting()
const moduleData = {
header: [
{ name: getMessage('module'), width: 150, prop: 'module', type: 'color-box' },
{
name: `${getMessage('height')} (mm)`,
prop: 'height',
},
{ name: `${getMessage('width')} (mm)`, prop: 'width' },
{ name: `${getMessage('output')} (W)`, prop: 'output' },
],
}
const allOption = {
moduleSerCd: 'ALL',
moduleSerNm: getMessage("board.sub.total") || 'ALL'
};
useEffect(() => {
if (basicSetting.roofSizeSet === '3') {
restoreModuleInstArea()
}
}, [])
useEffect(() => {
if (moduleSelectionData?.common) {
setInputMargin(moduleSelectionData?.common?.margin)
}
}, [moduleSelectionData])
useEffect(() => {
if (selectedModules) {
const foundModule = moduleList.find((module) => module.itemId === selectedModules.itemId)
if (foundModule) {
setSelectedModules(foundModule)
// ( )
if (moduleSeriesList.length > 0 && foundModule.moduleSerCd) {
const currentSeries = moduleSeriesList.find(series => series.moduleSerCd === foundModule.moduleSerCd)
if (currentSeries && (!selectedModuleSeries || selectedModuleSeries.moduleSerCd !== currentSeries.moduleSerCd)) {
//setSelectedModuleSeries(currentSeries)
}
}else{
setSelectedModuleSeries(allOption)
}
}
}
}, [selectedModules, moduleList, moduleSeriesList])
useEffect(() => {
if (selectedSurfaceType) {
setInputRoughness(roughnessCodes.find((code) => code.clCode === managementState?.surfaceTypeValue))
}
}, [selectedSurfaceType])
useEffect(() => {
if (standardWindSpeed) setInputStandardWindSpeed(windSpeedCodes.find((code) => code.clCode === managementState?.standardWindSpeedId))
}, [standardWindSpeed])
useEffect(() => {
if (managementState?.installHeight && managementState?.installHeight) {
setSelectedSurfaceType(roughnessCodes.find((code) => code.clCode === managementState?.surfaceTypeValue))
setInputInstallHeight(managementState?.installHeight)
setStandardWindSpeed(windSpeedCodes.find((code) => code.clCode === managementState?.standardWindSpeedId))
setInputVerticalSnowCover(managementState?.verticalSnowCover)
}
}, [managementState])
useImperativeHandle(ref, () => ({
handleNextStep,
}))
const handleNextStep = () => {
nextStep()
canvasPopupStatusTrigger(compasDeg)
if (isComplete()) {
const common = {
illuminationTp: inputRoughness.clCode,
illuminationTpNm: inputRoughness.clCodeNm,
instHt: inputInstallHeight,
stdWindSpeed: inputStandardWindSpeed?.clCode,
stdSnowLd: inputVerticalSnowCover,
saleStoreNorthFlg: managementState?.saleStoreNorthFlg,
moduleTpCd: selectedModules.itemTp,
moduleItemId: selectedModules.itemId,
margin: inputMargin,
}
setCompasDeg(inputCompasDeg)
setInstallHeight(inputInstallHeight)
setVerticalSnowCover(inputVerticalSnowCover)
setSelectedSurfaceType(inputRoughness)
setStandardWindSpeed(inputStandardWindSpeed)
nextStep(inputCompasDeg)
setManagementState({
...managementState,
installHeight: inputInstallHeight,
verticalSnowCover: inputVerticalSnowCover,
standardWindSpeedId: inputStandardWindSpeed?.clCode,
surfaceType: inputRoughness.clCodeNm,
surfaceTypeValue: inputRoughness.clCode,
})
setModuleSelectionData({
...moduleSelectionData,
module: {
...selectedModules,
},
common,
})
orientationTrigger({
compasDeg: inputCompasDeg,
common: common,
module: {
...selectedModules,
},
margin: inputMargin,
})
updateObjectDataApi({
objectNo: currentCanvasPlan.objectNo, //_no
standardWindSpeedId: inputStandardWindSpeed?.clCode, //
verticalSnowCover: inputVerticalSnowCover, //
surfaceType: inputRoughness.clCodeNm, //
installHeight: inputInstallHeight, //
userId: loginUserState.userId, //
})
setTabNum(2)
} else {
if (!selectedModules || !selectedModules.itemId) {
Swal.fire({
title: getMessage('module.not.found'),
icon: 'warning',
})
useEffect(() => {
checkDegree(compasDeg)
}, [compasDeg])
}
}
}
const checkDegree = (e) => {
if (e === '-0' || e === '-') {
setCompasDeg('-')
setInputCompasDeg('-')
return
}
if (e === '0-') {
setCompasDeg('-0')
setInputCompasDeg('-0')
return
}
if (Number(e) >= -180 && Number(e) <= 180) {
if (numberCheck(Number(e))) {
setCompasDeg(Number(e))
const n = Number(normalizeDecimal(e))
if (n >= -180 && n <= 180) {
if (numberCheck(n)) {
setInputCompasDeg(n)
}
} else {
setCompasDeg(compasDeg)
setInputCompasDeg(compasDeg)
}
}
const isComplete = () => {
if (!selectedModules || !selectedModules.itemId) return false
if (basicSetting && basicSetting.roofSizeSet !== '3') {
if (inputInstallHeight <= 0) {
return false
}
if (+inputVerticalSnowCover <= 0) {
return false
}
if (!inputStandardWindSpeed) return false
if (!inputRoughness) return false
}
return true
}
const handleChangeModuleSeries = (e) => {
resetRoofs()
setSelectedModuleSeries(e)
//
if (e && moduleList.length > 0) {
let filtered
if (e.moduleSerCd === 'ALL') {
// ""
filtered = moduleList
} else {
//
//filtered = moduleList.filter(module => module.moduleSerCd === e.moduleSerCd)
filtered = moduleList.filter(module => module && module.moduleSerCd && module.moduleSerCd === e.moduleSerCd)
}
setFilteredModuleList(filtered)
//
if (filtered.length > 0) {
const firstModule = filtered[0]
setSelectedModules(firstModule)
// handleChangeModule
if (handleChangeModule) {
handleChangeModule(firstModule)
}
}
} else {
//
setFilteredModuleList([])
setSelectedModules(null)
}
}
const handleChangeModule = (e) => {
resetRoofs()
setSelectedModules(e)
}
const handleChangeRoughness = (e) => {
resetRoofs()
setInputRoughness(e)
}
const handleChangeInstallHeight = (e) => {
resetRoofs()
setInputInstallHeight(e)
}
const handleChangeStandardWindSpeed = (e) => {
resetRoofs()
setInputStandardWindSpeed(e)
}
const handleChangeVerticalSnowCover = (e) => {
resetRoofs()
setInputVerticalSnowCover(e)
}
const resetRoofs = () => {
const newRoofs = addedRoofs.map((roof) => {
return {
...roof,
trestle: {
lengthBase: null,
trestleMkrCd: null,
constMthdCd: null,
constTp: null,
roofBaseCd: null,
roofPchBase: null,
},
addRoof: {
...roof.addRoof,
lengthBase: null,
eavesMargin: null,
kerabaMargin: null,
ridgeMargin: null,
},
construction: {
constTp: null,
cvrYn: 'N',
snowGdPossYn: 'N',
cvrChecked: false,
snowGdChecked: false,
},
}
})
// setRoofs(newRoofs)
// setAddedRoofs(newRoofs)
setRoofsStore(newRoofs)
}
// commonCode
useEffect(() => {
if (moduleList.length > 0 && moduleSeriesList.length === 0) {
const moduleSeriesCodes = findCommonCode(207100) || []
// moduleList moduleSerCd
const uniqueSeriesCd = [...new Set(moduleList.map(module => module.moduleSerCd).filter(Boolean))]
if (uniqueSeriesCd.length > 0) {
// moduleSerCd commonCode moduleSeriesList
const mappedSeries = uniqueSeriesCd.map(serCd => {
const matchedCode = moduleSeriesCodes.find(code => code.clCode === serCd)
return {
moduleSerCd: serCd,
moduleSerNm: matchedCode ? matchedCode.clCodeNm : serCd
}
})
// ""
const seriesList = [allOption, ...mappedSeries]
setModuleSeriesList(seriesList)
//
if (selectedModules && selectedModules.moduleSerCd) {
const currentSeries = seriesList.find(series => series.moduleSerCd === selectedModules.moduleSerCd)
if (currentSeries) {
setSelectedModuleSeries(currentSeries)
} else {
setSelectedModuleSeries(allOption)
// "ALL"
setTimeout(() => handleChangeModuleSeries(allOption), 0)
}
} else {
// ""
setSelectedModuleSeries(allOption)
// "ALL"
setTimeout(() => handleChangeModuleSeries(allOption), 0)
}
}
}
}, [moduleList, selectedModules])
//
useEffect(() => {
if (moduleList.length > 0 && filteredModuleList.length === 0 && selectedModuleSeries) {
let filtered
if (selectedModuleSeries.moduleSerCd === 'ALL') {
// ""
filtered = moduleList
} else {
//
filtered = moduleList.filter(module => module.moduleSerCd === selectedModuleSeries.moduleSerCd)
}
setFilteredModuleList(filtered)
if (filtered.length > 0 && !selectedModules) {
setSelectedModules(filtered[0])
}
} else if (moduleList.length === 0 && filteredModuleList.length === 0 && selectedModuleSeries) {
//
setFilteredModuleList([])
}
}, [moduleList, selectedModuleSeries]);
return (
<>
<div className="properties-setting-wrap">
<div className="outline-wrap">
<div className="roof-module-inner">
<div className="compas-wrapper">
<div className="guide">{getMessage('modal.module.basic.setting.orientation.setting.info')}</div>
<div className="roof-module-compas">
<div className="compas-box">
@ -56,60 +401,265 @@ export const Orientation = forwardRef(({ tabNum }, ref) => {
{Array.from({ length: 180 / 15 }).map((dot, index) => (
<div
key={index}
className={`circle ${getDegreeInOrientation(compasDeg) === -1 * (-15 * index + 180) || (index === 0 && compasDeg >= 172 && index === 0 && compasDeg <= 180) || (compasDeg === -180 && index === 0) ? 'act' : ''}`}
className={`circle ${getDegreeInOrientation(inputCompasDeg) === -15 * index + 180 || (index === 0 && inputCompasDeg >= 172 && index === 0 && inputCompasDeg <= 180) || (inputCompasDeg === -180 && index === 0) ? 'act' : ''}`}
onClick={() => {
if (index === 0) {
setCompasDeg(180)
setInputCompasDeg(180)
return
}
setCompasDeg(-1 * (-15 * index + 180))
setInputCompasDeg(-15 * index + 180)
}}
>
{index === 0 && <i>180°</i>}
{index === 6 && <i>-90°</i>}
{index === 6 && <i>90°</i>}
</div>
))}
{Array.from({ length: 180 / 15 }).map((dot, index) => (
<div
key={index}
className={`circle ${compasDeg !== 180 && getDegreeInOrientation(compasDeg) === 15 * index ? 'act' : ''}`}
onClick={() => setCompasDeg(15 * index)}
className={`circle ${inputCompasDeg !== 180 && getDegreeInOrientation(inputCompasDeg) === -1 * 15 * index ? 'act' : ''}`}
onClick={() => setInputCompasDeg(15 * index * -1)}
>
{index === 0 && <i>0°</i>}
{index === 6 && <i>90°</i>}
{index === 6 && <i>-90°</i>}
</div>
))}
<div className="compas">
<div className="compas-arr" style={{ transform: `rotate(${getDegreeInOrientation(compasDeg)}deg)` }}></div>
<div className="compas-arr" style={{ transform: `rotate(${-1 * getDegreeInOrientation(inputCompasDeg)}deg)` }}></div>
</div>
</div>
</div>
</div>
<div className="center-wrap">
<div className="d-check-box pop">
<input type="checkbox" id="ch99" checked={hasAnglePassivity} onChange={() => setHasAnglePassivity(!hasAnglePassivity)} />
<label htmlFor="ch99">{getMessage('modal.module.basic.setting.orientation.setting.angle.passivity')}-180 180</label>
</div>
<div className="outline-form">
<div className="input-grid mr10" style={{ width: '160px' }}>
<input
type="text"
<div className="d-check-box pop mr10">
<input type="checkbox" id="ch99" checked={hasAnglePassivity} onChange={() => setHasAnglePassivity(!hasAnglePassivity)} />
<label htmlFor="ch99">{getMessage('modal.module.basic.setting.orientation.setting.angle.passivity')}</label>
</div>
<div className="input-grid mr10" style={{ width: '60px' }}>
{/*<input*/}
{/* type="text"*/}
{/* className="input-origin block"*/}
{/* value={inputCompasDeg}*/}
{/* readOnly={!hasAnglePassivity}*/}
{/* placeholder={0}*/}
{/* onChange={(e) => checkDegree(e.target.value)}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={compasDeg}
value={inputCompasDeg}
readOnly={!hasAnglePassivity}
placeholder={0}
onChange={
(e) => checkDegree(e.target.value)
// setCompasDeg(
// e.target.value === '-' || (e.target.value !== '' && parseInt(e.target.value) <= 180 && parseInt(e.target.value) >= -180)
// ? e.target.value
// : 0,
// )
onChange={(value) => {
// Convert to number and ensure it's within -180 to 180 range
const numValue = parseInt(value, 10);
if (!isNaN(numValue)) {
const clampedValue = Math.min(180, Math.max(-180, numValue));
setInputCompasDeg(String(clampedValue));
} else {
setInputCompasDeg(value);
}
}}
options={{
allowNegative: true,
allowDecimal: false
}}
/>
</div>
<span className="thin">°</span>
<span className="thin"> -180 180 </span>
</div>
</div>
</div>
<div className="compas-table-wrap">
<div className="compas-table-box mb10">
<div className="outline-form mb10">
<span>{getMessage('modal.module.basic.setting.module.series.setting')}</span>
<div className="grid-select">
<div className="grid-select">
<QSelectBox
options={moduleSeriesList.length > 0 ? moduleSeriesList : [allOption]}
value={selectedModuleSeries}
targetKey={'moduleSerCd'}
sourceKey={'moduleSerCd'}
showKey={'moduleSerNm'}
onChange={(e) => handleChangeModuleSeries(e)}
/>
</div>
</div>
</div>
<div className="outline-form mb10">
<span>{getMessage('modal.module.basic.setting.module.setting2')}</span>
<div className="grid-select">
{filteredModuleList && (
<QSelectBox
options={filteredModuleList}
value={selectedModules}
targetKey={'itemId'}
sourceKey={'itemId'}
showKey={'itemNm'}
onChange={(e) => handleChangeModule(e)}
showFirstOptionWhenEmpty = {true}
/>
)}
</div>
</div>
<div className="roof-module-table">
<table>
<thead>
<tr>
{moduleData.header.map((header) => {
return (
<th key={header.prop} style={{ width: header.width ? header.width + 'px' : '' }}>
{header.name}
</th>
)
})}
</tr>
</thead>
<tbody>
{Array.from({ length: 3 }).map((_, index) => {
return selectedModules && selectedModules?.itemList && selectedModules?.itemList?.length >= index + 1 ? (
<tr key={index}>
<td>
<div className="color-wrap">
<span
className="color-box"
style={{
backgroundColor: selectedModules.itemList[index].color,
}}
></span>
<span className="name">{selectedModules.itemList[index].itemNm}</span>
</div>
</td>
<td className="al-r">{Number(selectedModules.itemList[index].shortAxis).toFixed(0)}</td>
<td className="al-r">{Number(selectedModules.itemList[index].longAxis).toFixed(0)}</td>
<td className="al-r">{Number(selectedModules.itemList[index].wpOut).toFixed(0)}</td>
</tr>
) : (
<tr key={index}>
<td>
<div className="color-wrap"></div>
</td>
<td className="al-r"></td>
<td className="al-r"></td>
<td className="al-r"></td>
</tr>
)
})}
</tbody>
</table>
</div>
{basicSetting && basicSetting.roofSizeSet === '3' && (
<div className="outline-form mt15">
<span>{getMessage('modal.module.basic.setting.module.placement.area')}</span>
<div className="input-grid mr10" style={{ width: '60px' }}>
{/*<input type="text" className="input-origin block" value={inputMargin} onChange={(e) => setInputMargin(normalizeDecimal(e.target.value))} />*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={inputMargin}
onChange={(value) => setInputMargin(value)}
options={{
allowNegative: false,
allowDecimal: false
}}
/>
</div>
<span className="thin">m</span>
</div>
)}
</div>
{basicSetting && basicSetting.roofSizeSet !== '3' && (
<div className="compas-table-box">
<div className="compas-grid-table">
<div className="outline-form">
<span>{getMessage('modal.module.basic.setting.module.surface.type')}</span>
<div className="grid-select">
{roughnessCodes.length > 0 && managementState && (
<QSelectBox
options={roughnessCodes}
value={inputRoughness}
targetKey={'clCode'}
sourceKey={'clCode'}
showKey={'clCodeNm'}
onChange={(e) => handleChangeRoughness(e)}
/>
)}
</div>
</div>
<div className="outline-form">
<span>{getMessage('modal.module.basic.setting.module.fitting.height')}</span>
<div className="input-grid mr10">
{/*<input*/}
{/* type="text"*/}
{/* className="input-origin block"*/}
{/* value={inputInstallHeight}*/}
{/* onChange={(e) => handleChangeInstallHeight(normalizeDecimal(e.target.value))}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={inputInstallHeight}
onChange={(value) => handleChangeInstallHeight(value)}
options={{
allowNegative: false,
allowDecimal: false
}}
/>
</div>
<span className="thin">m</span>
</div>
<div className="outline-form">
<span>{getMessage('modal.module.basic.setting.module.standard.wind.speed')}</span>
<div className="grid-select">
{windSpeedCodes.length > 0 && managementState && (
<QSelectBox
title={''}
options={windSpeedCodes}
value={inputStandardWindSpeed}
targetKey={'clCode'}
sourceKey={'clCode'}
showKey={'clCodeNm'}
onChange={(e) => handleChangeStandardWindSpeed(e)}
/>
)}
</div>
</div>
<div className="outline-form">
<span>{getMessage('modal.module.basic.setting.module.standard.snowfall.amount')}</span>
<div className="input-grid mr10">
{/*<input*/}
{/* type="text"*/}
{/* className="input-origin block"*/}
{/* value={inputVerticalSnowCover}*/}
{/* onChange={(e) => handleChangeVerticalSnowCover(normalizeDecimal(e.target.value))}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={inputVerticalSnowCover}
onChange={(value) => handleChangeVerticalSnowCover(value)}
options={{
allowNegative: false,
allowDecimal: false
}}
/>
</div>
<span className="thin">cm</span>
</div>
</div>
</div>
)}
</div>
</div>
</div>

View File

@ -1,29 +1,46 @@
import { forwardRef, useEffect, useState } from 'react'
import { useMessage } from '@/hooks/useMessage'
import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting'
import { checkedModuleState, currentCanvasPlanState, isManualModuleSetupState } from '@/store/canvasAtom'
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
import {
checkedModuleState,
isManualModuleLayoutSetupState,
isManualModuleSetupState,
moduleRowColArrayState,
moduleSetupOptionState,
toggleManualSetupModeState,
} from '@/store/canvasAtom'
import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
import { isObjectNotEmpty } from '@/util/common-utils'
import { normalizeDigits } from '@/util/input-utils'
import Image from 'next/image'
const Placement = forwardRef((props, refs) => {
const { getMessage } = useMessage()
const [isChidori, setIsChidori] = useState(false)
const [useTab, setUseTab] = useState(true)
const [guideType, setGuideType] = useState('batch')
const [isChidoriNotAble, setIsChidoriNotAble] = useState(false)
const [setupLocation, setSetupLocation] = useState('eaves')
const [isMaxSetup, setIsMaxSetup] = useState('false')
const [selectedItems, setSelectedItems] = useState({})
const [selectedModules, setSelectedModules] = useRecoilState(selectedModuleState)
const setCheckedModules = useSetRecoilState(checkedModuleState)
const moduleSelectionData = useRecoilValue(moduleSelectionDataState)
const { makeModuleInitArea } = useModuleBasicSetting(3)
const { makeModuleInitArea, roofOutlineColor } = useModuleBasicSetting(3)
const [isMultiModule, setIsMultiModule] = useState(false)
const [isManualModuleSetup, setIsManualModuleSetup] = useRecoilState(isManualModuleSetupState)
//
const setIsManualModuleSetup = useSetRecoilState(isManualModuleSetupState)
const setIsManualModuleLayoutSetup = useSetRecoilState(isManualModuleLayoutSetupState)
const setManualSetupMode = useSetRecoilState(toggleManualSetupModeState)
const [moduleSetupOption, setModuleSetupOption] = useRecoilState(moduleSetupOptionState) //
const resetModuleSetupOption = useResetRecoilState(moduleSetupOptionState)
const [colspan, setColspan] = useState(1)
const moduleRowColArray = useRecoilValue(moduleRowColArrayState)
//
useEffect(() => {
@ -36,11 +53,24 @@ const Placement = forwardRef((props, refs) => {
makeModuleInitArea(moduleSelectionData)
}
if (moduleSelectionData.module.itemList.length > 1) {
setColspan(2)
}
return () => {
// refs.isChidori.current = 'false'
// refs.setupLocation.current = 'eaves'
setIsManualModuleSetup(false)
setIsManualModuleLayoutSetup(false)
setManualSetupMode('off')
resetModuleSetupOption()
}
}, [])
// useEffect(() => {
// console.log('moduleRowColArray', moduleRowColArray)
// }, [moduleRowColArray])
//
useEffect(() => {
if (isObjectNotEmpty(moduleSelectionData)) {
@ -54,8 +84,10 @@ const Placement = forwardRef((props, refs) => {
initCheckedModule = { ...initCheckedModule, [obj.itemId]: true }
}
})
setSelectedItems(initCheckedModule)
setSelectedModules(moduleSelectionData.module)
props.setLayoutSetup(moduleSelectionData.module.itemList.map((item) => ({ moduleId: item.itemId, col: 0, row: 0, checked: true })))
}
//
@ -80,40 +112,58 @@ const Placement = forwardRef((props, refs) => {
header: [
{ type: 'check', name: '', prop: 'check', width: 70 },
{ type: 'color-box', name: getMessage('module'), prop: 'module' },
{ type: 'text', name: `${getMessage('output')} (W)`, prop: 'output', width: 70 },
{ type: 'text', name: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn'), prop: 'mixAsgYn', width: 50 },
{ type: 'text', name: `段数`, prop: 'rows', width: 60 },
{ type: 'text', name: `列数`, prop: 'cols', width: 60 },
],
rows: [],
}
const handleChangeChidori = (e) => {
const bool = e.target.value === 'true' ? true : false
setIsChidori(bool)
refs.isChidori.current = e.target.value
setModuleSetupOption({ ...moduleSetupOption, isChidori: bool })
//
setIsManualModuleSetup(false)
setIsManualModuleLayoutSetup(false)
setManualSetupMode('off')
}
const handleSetupLocation = (e) => {
setSetupLocation(e.target.value)
refs.setupLocation.current = e.target.value
}
setModuleSetupOption({ ...moduleSetupOption, setupLocation: e.target.value })
const handleMaxSetup = (e) => {
if (e.target.checked) {
setIsMaxSetup('true')
refs.isMaxSetup.current = 'true'
} else {
setIsMaxSetup('false')
refs.isMaxSetup.current = 'false'
}
//
setIsManualModuleSetup(false)
setIsManualModuleLayoutSetup(false)
setManualSetupMode('off')
}
//
const handleSelectedItem = (e) => {
const handleSelectedItem = (e, itemId) => {
setSelectedItems({ ...selectedItems, [e.target.name]: e.target.checked })
const newLayoutSetup = [...props.layoutSetup]
props.layoutSetup.forEach((item, index) => {
if (item.moduleId === itemId) {
newLayoutSetup[index] = { ...props.layoutSetup[index], checked: e.target.checked }
}
})
props.setLayoutSetup(newLayoutSetup)
}
const handleLayoutSetup = (e, itemId, index) => {
const newLayoutSetup = [...props.layoutSetup]
newLayoutSetup[index] = {
...newLayoutSetup[index],
moduleId: itemId,
[e.target.name]: Number(normalizeDigits(e.target.value)),
}
props.setLayoutSetup(newLayoutSetup)
}
return (
<>
<div className="module-table-flex-wrap mb10">
<div className="module-table-flex-wrap">
<div className="module-table-box">
<div className="module-table-inner">
<div className="roof-module-table">
@ -135,8 +185,8 @@ const Placement = forwardRef((props, refs) => {
</tr>
</thead>
<tbody>
{selectedModules.itemList &&
selectedModules.itemList.map((item, index) => (
{selectedModules?.itemList &&
selectedModules?.itemList?.map((item, index) => (
<tr key={index}>
<td className="al-c">
<div className="d-check-box no-text pop">
@ -145,7 +195,7 @@ const Placement = forwardRef((props, refs) => {
id={item.itemId}
name={item.itemId}
checked={selectedItems[item.itemId]}
onChange={handleSelectedItem}
onChange={(e) => handleSelectedItem(e, item.itemId)}
/>
<label htmlFor={item.itemId}></label>
</div>
@ -156,7 +206,199 @@ const Placement = forwardRef((props, refs) => {
<span className="name">{item.itemNm}</span>
</div>
</td>
<td className="al-r">{item.wpOut}</td>
<td className="al-c">
<div className="color-wrap">
<span className="name">{item.mixAsgYn}</span>
</div>
</td>
<td className="al-r">
<div className="input-grid">
<input
type="text"
className="input-origin block"
name="row"
value={props.layoutSetup[index]?.row ?? 1}
//defaultValue={0}
onChange={(e) => handleLayoutSetup(e, item.itemId, index)}
/>
</div>
</td>
<td className="al-r">
<div className="input-grid">
<input
type="text"
className="input-origin block"
name="col"
value={props.layoutSetup[index]?.col ?? 1}
//defaultValue={0}
onChange={(e) => handleLayoutSetup(e, item.itemId, index)}
/>
</div>
</td>
</tr>
))}
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div className="module-table-box non-flex">
<div className="module-table-inner">
<div className="roof-module-table">
<table>
<thead>
<tr>
<th>{getMessage('modal.module.basic.setting.module.placement.waterfowl.arrangement')}</th>
<th>{getMessage('modal.module.basic.setting.module.placement.arrangement.standard')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div className="hexagonal-radio-wrap">
<div className="d-check-radio pop mb10">
<input
type="radio"
name="radio02"
id="ra03"
checked={moduleSetupOption.isChidori}
disabled={isChidoriNotAble}
value={'true'}
onChange={(e) => handleChangeChidori(e)}
/>
<label htmlFor="ra03">{getMessage('modal.module.basic.setting.module.placement.do')}</label>
</div>
<div className="d-check-radio pop">
<input
type="radio"
name="radio02"
id="ra04"
checked={!moduleSetupOption.isChidori}
value={'false'}
onChange={(e) => handleChangeChidori(e)}
/>
<label htmlFor="ra04">{getMessage('modal.module.basic.setting.module.placement.do.not')}</label>
</div>
</div>
</td>
<td>
<div className="hexagonal-radio-wrap">
<div className="d-check-radio pop mb10">
<input
type="radio"
name="radio03"
id="ra05"
checked={moduleSetupOption.setupLocation === 'eaves'}
value={'eaves'}
onChange={handleSetupLocation}
/>
<label htmlFor="ra05">{getMessage('modal.module.basic.setting.module.placement.arrangement.standard.eaves')}</label>
</div>
<div className="d-check-radio pop">
<input
type="radio"
name="radio03"
id="ra06"
checked={moduleSetupOption.setupLocation === 'ridge'}
value={'ridge'}
onChange={handleSetupLocation}
disabled={isMultiModule}
/>
<label htmlFor="ra06">{getMessage('modal.module.basic.setting.module.placement.arrangement.standard.ridge')}</label>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div className="hide-tab-wrap">
<div className="hide-check-guide">
{getMessage('modal.module.basic.setting.module.placement.info')}
<button className={`arr ${useTab ? 'act' : ''}`} onClick={() => setUseTab(!useTab)}></button>
</div>
<div className={`hide-tab-contents ${!useTab ? 'hide' : ''}`}>
<div className="roof-content-tab-wrap">
<button className={`btn-frame block modal mr5 ${guideType === 'batch' ? 'act' : ''} `} onClick={() => setGuideType('batch')}>
{getMessage('modal.module.basic.setting.module.placement.info.batch')}
</button>
<button className={`btn-frame block modal mr5 ${guideType === 'module' ? 'act' : ''}`} onClick={() => setGuideType('module')}>
{getMessage('modal.module.basic.setting.module.placement.info.module')}
</button>
</div>
{guideType === 'batch' && (
<div className={`roof-warning-wrap mt10`}>
<div className="guide">
{getMessage('modal.module.basic.setting.module.placement.info.batch.content1')}
<br />
{getMessage('modal.module.basic.setting.module.placement.info.batch.content2')}
</div>
<div className="roof-warning-img-wrap">
<div className="roof-warning-img">
<Image src={'/static/images/canvas/roof_warning_correct.png'} width={350} height={198} alt="" />
</div>
<div className="roof-warning-img">
<Image src={'/static/images/canvas/roof_warning_wrong.png'} width={350} height={198} alt="" />
</div>
</div>
</div>
)}
{guideType === 'module' && (
<div className={`module-table-box mt10 ${!useTab ? 'hide' : ''}`}>
<div className="module-table-inner">
<div className="roof-module-table">
<table className="">
<thead>
<tr>
<th rowSpan={2} style={{ width: '22%' }}></th>
{selectedModules &&
selectedModules.itemList?.map((item) => (
// <th colSpan={colspan}>
<th>
<div className="color-wrap">
<span className="color-box" style={{ backgroundColor: item.color }}></span>
<span className="name">{item.itemNm}</span>
</div>
</th>
))}
{colspan > 1 && <th rowSpan={2}>{getMessage('modal.module.basic.setting.module.placement.max.rows.multiple')}</th>}
</tr>
<tr>
{selectedModules &&
selectedModules.itemList?.map((item) => (
<>
<th>{getMessage('modal.module.basic.setting.module.placement.max.row')}</th>
{/* {colspan > 1 && <th>{getMessage('modal.module.basic.setting.module.placement.max.rows.multiple')}</th>} */}
</>
))}
</tr>
</thead>
<tbody>
{moduleSelectionData.roofConstructions.map((item, index) => (
<tr>
<td>
<div className="color-wrap">
<span className="color-box" style={{ backgroundColor: roofOutlineColor(item.addRoof?.index) }}></span>
<span className="name">{item.addRoof?.roofMatlNmJp}</span>
</div>
</td>
{moduleRowColArray[index]?.map((item, index2) => (
<>
<td className="al-c">{item.moduleMaxRows}</td>
{/* {colspan > 1 && <td className="al-c">{item.mixModuleMaxRows}</td>} */}
{colspan > 1 && index2 === moduleRowColArray[index].length - 1 && <td className="al-c">{item.maxRow}</td>}
</>
))}
</tr>
))}
</tbody>
@ -164,83 +406,7 @@ const Placement = forwardRef((props, refs) => {
</div>
</div>
</div>
<div className="module-table-box">
<div className="module-table-inner">
<div className="self-table-tit">{getMessage('modal.module.basic.setting.module.placement.select.fitting.type')}</div>
<div className="module-self-table">
<div className="self-table-item">
<div className="self-item-th">{getMessage('modal.module.basic.setting.module.placement.waterfowl.arrangement')}</div>
<div className="self-item-td">
<div className="pop-form-radio">
<div className="d-check-radio pop">
<input
type="radio"
name="radio01"
id="ra01"
checked={isChidori}
disabled={isChidoriNotAble}
value={'true'}
onChange={(e) => handleChangeChidori(e)}
/>
<label htmlFor="ra01">{getMessage('modal.module.basic.setting.module.placement.do')}</label>
</div>
<div className="d-check-radio pop">
<input type="radio" name="radio02" id="ra02" checked={!isChidori} value={'false'} onChange={(e) => handleChangeChidori(e)} />
<label htmlFor="ra02">{getMessage('modal.module.basic.setting.module.placement.do.not')}</label>
</div>
</div>
</div>
</div>
<div className="self-table-item">
<div className="self-item-th">{getMessage('modal.module.basic.setting.module.placement.arrangement.standard')}</div>
<div className="self-item-td">
<div className="pop-form-radio">
<div className="d-check-radio pop">
<input
type="radio"
name="radio03"
id="ra03"
checked={setupLocation === 'center'}
value={'center'}
onChange={handleSetupLocation}
disabled={isMultiModule}
/>
<label htmlFor="ra03">{getMessage('modal.module.basic.setting.module.placement.arrangement.standard.center')}</label>
</div>
<div className="d-check-radio pop">
<input
type="radio"
name="radio04"
id="ra04"
checked={setupLocation === 'eaves'}
value={'eaves'}
onChange={handleSetupLocation}
/>
<label htmlFor="ra04">{getMessage('modal.module.basic.setting.module.placement.arrangement.standard.eaves')}</label>
</div>
<div className="d-check-radio pop">
<input
type="radio"
name="radio05"
id="ra05"
checked={setupLocation === 'ridge'}
value={'ridge'}
onChange={handleSetupLocation}
disabled={isMultiModule}
/>
<label htmlFor="ra05">{getMessage('modal.module.basic.setting.module.placement.arrangement.standard.ridge')}</label>
</div>
</div>
</div>
</div>
</div>
<div className="self-table-flx">
{/* <div className="d-check-box pop">
<input type="checkbox" id="ch04" checked={isMaxSetup === 'true'} value={'true'} onChange={handleMaxSetup} />
<label htmlFor="ch04">{getMessage('modal.module.basic.setting.module.placement.maximum')}</label>
</div> */}
</div>
</div>
)}
</div>
</div>
</>

View File

@ -0,0 +1,999 @@
import { GlobalDataContext } from '@/app/GlobalDataProvider'
import QSelectBox from '@/components/common/select/QSelectBox'
import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting'
import { useModuleTrestle } from '@/hooks/module/useModuleTrestle'
import { useMessage } from '@/hooks/useMessage'
import { currentAngleTypeSelector, pitchTextSelector } from '@/store/canvasAtom'
import { roofsState } from '@/store/roofAtom'
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
import { forwardRef, useContext, useEffect, useImperativeHandle, useRef, useState } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import Swal from 'sweetalert2'
import { normalizeDigits } from '@/util/input-utils'
import { CalculatorInput } from '@/components/common/input/CalcInput'
const Trestle = forwardRef((props, ref) => {
const { tabNum, setTabNum, trestleTrigger, roofs, setRoofs, moduleSelectionData, setModuleSelectionData, setRoofsStore } = props
const { getMessage } = useMessage()
// const [selectedTrestle, setSelectedTrestle] = useState()
const currentAngleType = useRecoilValue(currentAngleTypeSelector)
const pitchText = useRecoilValue(pitchTextSelector)
const [selectedRoof, setSelectedRoof] = useState(null)
const [isAutoSelecting, setIsAutoSelecting] = useState(false) //
const [autoSelectTimeout, setAutoSelectTimeout] = useState(null) //
const autoSelectTimeoutRef = useRef(null)
// ()
const AUTO_SELECT_TIMEOUT = 500 // API
const {
trestleState,
trestleDetail,
dispatch,
raftBaseList,
trestleList,
constMthdList,
roofBaseList,
constructionList,
eavesMargin,
ridgeMargin,
kerabaMargin,
setEavesMargin,
setRidgeMargin,
setKerabaMargin,
lengthBase,
setLengthBase,
hajebichi,
setHajebichi,
cvrYn,
cvrChecked,
snowGdPossYn,
snowGdChecked,
setCvrYn,
setCvrChecked,
setSnowGdPossYn,
setSnowGdChecked,
} = useModuleTrestle({
selectedRoof,
})
const selectedModules = useRecoilValue(selectedModuleState) //
// const [moduleSelectionData, setModuleSelectionData] = useRecoilState(moduleSelectionDataState)
const [selectedRaftBase, setSelectedRaftBase] = useState(null)
const [selectedTrestle, setSelectedTrestle] = useState(null)
const [selectedConstMthd, setSelectedConstMthd] = useState(null)
const [selectedConstruction, setSelectedConstruction] = useState(null)
const [selectedRoofBase, setSelectedRoofBase] = useState(null)
const { managementState } = useContext(GlobalDataContext)
const { restoreModuleInstArea } = useModuleBasicSetting()
const [flag, setFlag] = useState(false)
const tempModuleSelectionData = useRef(null)
const [autoSelectStep, setAutoSelectStep] = useState(null) // 'raftBase', 'trestle', 'constMthd', 'roofBase', 'construction'
const prevHajebichiRef = useRef();
useEffect(() => {
if (roofs && roofs.length > 0 && !selectedRoof) {
console.log("roofs:::::", roofs.length)
setLengthBase(roofs[0].length);
setSelectedRoof(roofs[0])
}
if (selectedRoof && selectedRoof.lenAuth === "C") {
onChangeLength(selectedRoof.length);
}else if (selectedRoof && ["C", "R"].includes(selectedRoof.raftAuth) && roofs && roofs.length > 0) {
onChangeRaftBase(roofs[0]);
}else if (selectedRoof && ["C", "R"].includes(selectedRoof.roofPchAuth) && roofs && roofs.length > 0 &&
roofs[0].hajebichi !== prevHajebichiRef.current ) {
prevHajebichiRef.current = roofs[0].hajebichi;
onChangeHajebichi(roofs[0].hajebichi);
}
//
restoreModuleInstArea()
}, [roofs, selectedRoof]) // selectedRoof
useEffect(() => {
if (flag && moduleSelectionData) {
if (JSON.stringify(tempModuleSelectionData.current) === JSON.stringify(moduleSelectionData)) {
setTabNum(tabNum + 1)
}
}
}, [flag, moduleSelectionData])
useEffect(() => {
if (selectedRoof) {
if (moduleSelectionData?.roofConstructions?.length >= selectedRoof.index + 1) {
const { construction, trestle, trestleDetail } = moduleSelectionData?.roofConstructions[selectedRoof.index]
dispatch({
type: 'SET_INITIALIZE',
roof: { common: moduleSelectionData.common, module: moduleSelectionData.module, construction, trestle, trestleDetail, ...selectedRoof },
})
} else {
dispatch({ type: 'SET_INITIALIZE', roof: { ...selectedRoof, common: moduleSelectionData.common, module: moduleSelectionData.module } })
}
}
}, [selectedRoof])
useEffect(() => {
if (raftBaseList.length > 0) {
setSelectedRaftBase(raftBaseList.find((raft) => raft.clCode === selectedRoof?.raft) ?? null)
} else {
setSelectedRaftBase(null)
}
}, [raftBaseList])
useEffect(() => {
if (trestleList.length > 0) {
const existingTrestle = trestleList.find(
(trestle) => trestle.trestleMkrCd === trestleState?.trestleMkrCd
);
if (existingTrestle) {
setSelectedTrestle(existingTrestle)
} else if (autoSelectStep === 'trestle') {
// :
console.log('Auto selecting first trestle:', trestleList[0])
const firstTrestle = trestleList[0]
onChangeTrestleMaker(firstTrestle)
// setAutoSelectStep onChangeTrestleMaker
}
} else {
setSelectedTrestle(null)
}
}, [trestleList, autoSelectStep])
useEffect(() => {
if (constMthdList.length > 0) {
const existingConstMthd = constMthdList.find((constMthd) => constMthd.constMthdCd === trestleState?.constMthdCd)
if (existingConstMthd) {
setSelectedConstMthd(existingConstMthd)
} else if (autoSelectStep === 'constMthd') {
// :
const firstConstMthd = constMthdList[0]
onChangeConstMthd(firstConstMthd)
setAutoSelectStep('roofBase') //
}
} else {
setSelectedConstMthd(null)
}
}, [constMthdList, autoSelectStep])
useEffect(() => {
if (roofBaseList.length > 0) {
const existingRoofBase = roofBaseList.find((roofBase) => roofBase.roofBaseCd === trestleState?.roofBaseCd)
if (existingRoofBase) {
setSelectedRoofBase(existingRoofBase)
} else if (autoSelectStep === 'roofBase') {
// :
const firstRoofBase = roofBaseList[0]
onChangeRoofBase(firstRoofBase)
setAutoSelectStep('construction') //
}
} else {
setSelectedRoofBase(null)
}
}, [roofBaseList, autoSelectStep])
useEffect(() => {
if (constructionList.length > 0) {
const existingConstruction = constructionList.find((construction) => construction.constTp === trestleState.constTp)
if (existingConstruction) {
setSelectedConstruction(existingConstruction)
} else if (autoSelectStep === 'construction') {
// : construction
const availableConstructions = constructionList.filter((construction) => construction.constPossYn === 'Y')
if (availableConstructions.length > 0) {
const firstConstruction = availableConstructions[0]
const firstIndex = constructionList.findIndex((construction) => construction.constTp === firstConstruction.constTp)
handleConstruction(firstIndex)
setAutoSelectStep(null) //
} else {
Swal.fire({
title: getMessage('modal.module.basic.settting.module.error4', [selectedRoof?.nameJp]),
icon: 'warning',
})
}
}
if (constructionList.filter((construction) => construction.constPossYn === 'Y').length === 0) {
Swal.fire({
title: getMessage('modal.module.basic.settting.module.error4', [selectedRoof?.nameJp]),
icon: 'warning',
})
}
} else {
setSelectedConstruction(null)
}
}, [constructionList, autoSelectStep])
const getConstructionState = (index) => {
if (constructionList && constructionList.length > 0) {
if (constructionList[index].constPossYn === 'Y') {
if (trestleState && trestleState.constTp === constructionList[index].constTp) {
return 'blue'
}
return 'white'
}
return 'no-click'
}
return 'no-click'
}
const onChangeLength = (e) => {
setLengthBase(e)
//
setSelectedRaftBase(null)
setSelectedTrestle(null)
setSelectedConstMthd(null)
setSelectedRoofBase(null)
setSelectedConstruction(null)
dispatch({
type: 'SET_LENGTH',
roof: {
length: e,
moduleTpCd: selectedModules.itemTp ?? '',
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
raft: selectedRaftBase?.clCode,
},
})
//
if (raftBaseList.length > 0) {
const inx = raftBaseList.findIndex((raft) => raft.clCode === selectedRoof?.raft) ?? 0
const firstRaftBase = raftBaseList[inx]
onChangeRaftBase(firstRaftBase)
}
}
const onChangeRaftBase = (e) => {
setSelectedRaftBase(e)
//
setSelectedTrestle(null)
setSelectedConstMthd(null)
setSelectedRoofBase(null)
setSelectedConstruction(null)
dispatch({
type: 'SET_RAFT_BASE',
roof: {
moduleTpCd: selectedModules.itemTp ?? '',
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
raft: e.clCode,
},
})
// () -
setTimeout(() => {
setAutoSelectStep('trestle')
}, AUTO_SELECT_TIMEOUT) // API
}
const onChangeHajebichi = (e) => {
setHajebichi(e)
//
setSelectedTrestle(null)
setSelectedConstMthd(null)
setSelectedRoofBase(null)
setSelectedConstruction(null)
// roofs selectedRoof.index
if (selectedRoof && selectedRoof.index !== undefined) {
const updatedRoofs = roofs.map((roof, index) => (index === selectedRoof.index ? { ...roof, hajebichi: Number(e) } : roof))
setRoofs(updatedRoofs)
}
dispatch({
type: 'SET_HAJEBICHI',
roof: {
moduleTpCd: selectedModules.itemTp ?? '',
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
hajebichi: e,
},
})
// () -
setTimeout(() => {
setAutoSelectStep('trestle')
}, AUTO_SELECT_TIMEOUT)
}
const onChangeTrestleMaker = (e) => {
setSelectedTrestle(e)
//
setSelectedConstMthd(null)
setSelectedRoofBase(null)
setSelectedConstruction(null)
dispatch({
type: 'SET_TRESTLE_MAKER',
roof: {
moduleTpCd: selectedModules.itemTp ?? '',
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
//hajebichi: selectedRaftBase?.hajebichi ?? selectedRoof?.hajebichi,
trestleMkrCd: e.trestleMkrCd,
},
})
// API ()
setTimeout(() => {
setAutoSelectStep('constMthd')
}, AUTO_SELECT_TIMEOUT)
}
const onChangeConstMthd = (e) => {
setSelectedConstMthd(e)
//
setSelectedRoofBase(null)
setSelectedConstruction(null)
dispatch({
type: 'SET_CONST_MTHD',
roof: {
moduleTpCd: selectedModules.itemTp ?? '',
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
//hajebichi: selectedRaftBase?.hajebichi ?? selectedRoof?.hajebichi,
trestleMkrCd: selectedTrestle?.trestleMkrCd,
constMthdCd: e.constMthdCd,
},
})
//
if (autoSelectTimeoutRef.current) {
clearTimeout(autoSelectTimeoutRef.current)
}
//
setIsAutoSelecting(true)
// API ()
const timeoutId = setTimeout(() => {
setAutoSelectStep('roofBase')
setIsAutoSelecting(false)
}, AUTO_SELECT_TIMEOUT)
autoSelectTimeoutRef.current = timeoutId
}
const onChangeRoofBase = (e) => {
setSelectedRoofBase(e)
setSelectedConstruction(null)
dispatch({
type: 'SET_ROOF_BASE',
roof: {
moduleTpCd: selectedModules.itemTp ?? '',
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
//hajebichi: selectedRaftBase?.hajebichi ?? selectedRoof?.hajebichi,
trestleMkrCd: selectedTrestle?.trestleMkrCd,
constMthdCd: selectedConstMthd?.constMthdCd,
roofBaseCd: e.roofBaseCd,
illuminationTp: managementState?.surfaceTypeValue ?? '',
instHt: managementState?.installHeight ?? '',
stdWindSpeed: managementState?.standardWindSpeedId ?? '',
stdSnowLd: managementState?.verticalSnowCover ?? '',
inclCd: selectedRoof?.pitch ?? 0,
roofPitch: Math.round(hajebichi ?? 0),
},
})
// API (construction)
setTimeout(() => {
setAutoSelectStep('construction')
}, AUTO_SELECT_TIMEOUT)
}
const handleConstruction = (index) => {
if (constructionList[index]?.constPossYn === 'Y') {
dispatch({
type: 'SET_CONSTRUCTION',
roof: {
moduleTpCd: selectedModules.itemTp ?? '',
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
//hajebichi: selectedRaftBase?.hajebichi ?? selectedRoof?.hajebichi,
trestleMkrCd: selectedTrestle.trestleMkrCd,
constMthdCd: selectedConstMthd.constMthdCd,
roofBaseCd: selectedRoofBase.roofBaseCd,
illuminationTp: managementState?.surfaceTypeValue ?? '',
instHt: managementState?.installHeight ?? '',
stdWindSpeed: managementState?.standardWindSpeedId ?? '',
stdSnowLd: managementState?.verticalSnowCover ?? '',
inclCd: selectedRoof?.pitch ?? 0,
roofPitch: Math.round(hajebichi ?? 0),
constTp: constructionList[index].constTp,
snowGdPossYn: constructionList[index].snowGdPossYn,
cvrYn: constructionList[index].cvrYn,
mixMatlNo: selectedModules.mixMatlNo,
// workingWidth: selectedRoof?.length?.toString() ?? '',
workingWidth: lengthBase,
},
})
setCvrYn(constructionList[index].cvrYn)
setSnowGdPossYn(constructionList[index].snowGdPossYn)
setCvrChecked(false)
setSnowGdChecked(false)
}
}
const handleChangeRoofMaterial = (index) => {
const newAddedRoofs = roofs.map((roof, i) => {
if (i === selectedRoof.index) {
return {
...selectedRoof,
hajebichi,
length: lengthBase,
eavesMargin,
ridgeMargin,
kerabaMargin,
roofIndex: selectedRoof.index,
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
trestle: {
hajebichi: hajebichi,
length: lengthBase,
...selectedRaftBase,
...selectedTrestle,
...selectedConstMthd,
...selectedRoofBase,
},
construction: {
...constructionList.find((data) => data.constTp === trestleState.constTp),
cvrYn: cvrYn,
snowGdPossYn: snowGdPossYn,
cvrChecked: cvrChecked,
snowGdChecked: snowGdChecked,
setupCover: cvrChecked ?? false,
setupSnowCover: snowGdChecked ?? false,
},
trestleDetail: trestleDetail,
}
}
return roof
})
setRoofs(newAddedRoofs)
setSelectedRoof(newAddedRoofs[index])
}
const isComplete = async () => {
const newAddedRoofs = roofs.map((roof, i) => {
if (i === selectedRoof?.index) {
return {
...selectedRoof,
length: lengthBase,
eavesMargin,
ridgeMargin,
kerabaMargin,
roofIndex: roof.index,
raft: selectedRaftBase?.clCode ?? selectedRoof?.raft ?? '',
//hajebichi: selectedRaftBase?.hajebichi ?? selectedRoof?.hajebichi ?? 0,
trestle: {
length: lengthBase,
hajebichi: hajebichi,
...selectedRaftBase,
...selectedTrestle,
...selectedConstMthd,
...selectedRoofBase,
},
construction: {
//...constructionList.find((data) => newAddedRoofs[index].construction.constTp === data.constTp),
...constructionList.find((data) => trestleState.constTp === data.constTp),
cvrYn,
snowGdPossYn,
cvrChecked,
snowGdChecked,
setupCover: cvrChecked ?? false,
setupSnowCover: snowGdChecked ?? false,
},
trestleDetail: trestleDetail,
}
}
return roof
})
let result = true
for (let i = 0; i < newAddedRoofs.length; i++) {
const roof = newAddedRoofs[i]
if (!roof.trestle?.trestleMkrCd) {
Swal.fire({
title: getMessage('modal.module.basic.settting.module.error1', [roof.nameJp]), // .
icon: 'warning',
})
result = false
return false
}
if (!roof.trestle?.constMthdCd) {
Swal.fire({
title: getMessage('modal.module.basic.settting.module.error2', [roof.nameJp]), // .
icon: 'warning',
})
result = false
return false
}
if (!roof.trestle?.roofBaseCd) {
Swal.fire({
title: getMessage('modal.module.basic.settting.module.error3', [roof.nameJp]), // .
icon: 'warning',
})
result = false
return false
}
if (!roof.construction?.constTp) {
Swal.fire({
title: getMessage('modal.module.basic.settting.module.error12', [roof.nameJp]), // .
icon: 'warning',
})
result = false
return false
}
if (roof.lenAuth === 'C') {
if (!roof.trestle?.length) {
Swal.fire({
title: getMessage('modal.module.basic.settting.module.error5', [roof.nameJp]), // L .
icon: 'warning',
})
result = false
return false
}
}
if (['C', 'R'].includes(roof.raftAuth)) {
if (!roof?.raft) {
Swal.fire({
title: getMessage('modal.module.basic.settting.module.error6', [roof.nameJp]), // .
icon: 'warning',
})
result = false
return false
}
}
if (['C', 'R'].includes(roof.roofPchAuth)) {
if (!roof?.hajebichi) {
Swal.fire({
title: getMessage('modal.module.basic.settting.module.error7', [roof.nameJp]), // .
icon: 'warning',
})
result = false
return false
}
}
if (!roof?.eavesMargin || !roof?.ridgeMargin || !roof?.kerabaMargin) {
Swal.fire({
title: getMessage('modal.module.basic.settting.module.error8', [roof.nameJp]), // .
icon: 'warning',
})
result = false
return false
}
if (roof.trestle.trestleMkrCd !== 'NO_DATA') {
//
if (roof.trestleDetail?.eaveIntvl > roof.eavesMargin) {
Swal.fire({
title: getMessage('modal.module.basic.settting.module.error9', [roof.trestleDetail?.eaveIntvl, roof.nameJp]), // {0}mm .
icon: 'warning',
})
result = false
return false
}
if (roof.trestleDetail?.ridgeIntvl > roof.ridgeMargin) {
Swal.fire({
title: getMessage('modal.module.basic.settting.module.error10', [roof.trestleDetail?.ridgeIntvl, roof.nameJp]), // {0}mm .
icon: 'warning',
})
result = false
return false
}
if (roof.trestleDetail?.kerabaIntvl > roof.kerabaMargin) {
Swal.fire({
title: getMessage('modal.module.basic.settting.module.error11', [roof.trestleDetail?.kerabaIntvl, roof.nameJp]), // {0}mm .
icon: 'warning',
})
result = false
return false
}
}
}
if (result) {
const newRoofs = newAddedRoofs.map((roof) => {
const { addRoof, construction, trestle, trestleDetail, roofConstructions, ...rest } = roof
return rest
})
setModuleSelectionData({
...moduleSelectionData,
roofConstructions: newAddedRoofs.map((roof, index) => ({
roofIndex: newRoofs[index].index,
trestle: roof.trestle,
addRoof: newRoofs[index],
construction: roof.construction,
trestleDetail: roof.trestleDetail,
})),
})
setFlag(true)
tempModuleSelectionData.current = {
...moduleSelectionData,
roofConstructions: newAddedRoofs.map((roof, index) => ({
roofIndex: newRoofs[index].index,
trestle: roof.trestle,
addRoof: newRoofs[index],
construction: roof.construction,
trestleDetail: roof.trestleDetail,
})),
}
const updatePromises = [
// new Promise((resolve) => {
// resolve()
// }),
new Promise((resolve) => {
setRoofs(newRoofs)
resolve()
}),
new Promise((resolve) => {
const roofConstructions = newAddedRoofs.map((roof, index) => ({
roofIndex: newRoofs[index].index,
addRoof: newRoofs[index],
trestle: {
...roof.trestle,
raft: roof.raftBaseCd,
},
construction: {
// ...constructionList.find((construction) => newAddedRoofs[index].construction.constTp === construction.constTp),
...roof.construction,
roofIndex: roof.index,
selectedIndex: roof.index,
},
trestleDetail: roof.trestleDetail,
}))
trestleTrigger({
roofConstructions,
})
setRoofsStore(roofConstructions)
resolve()
}),
]
await Promise.all(updatePromises)
return true
}
return false
}
useImperativeHandle(ref, () => ({
isComplete,
}))
return (
<div className="roof-module-tab2-overflow">
<div className="module-table-box mb10">
<div className="module-box-tab">
{roofs &&
roofs.map((roof, index) => (
<button
key={index}
className={`module-btn ${selectedRoof?.index === index ? 'act' : ''}`}
onClick={() => (roof ? handleChangeRoofMaterial(index) : null)}
>
{roof !== undefined ? `${roof.nameJp} (${currentAngleType === 'slope' ? roof.pitch : roof.angle}${pitchText})` : '-'}
</button>
))}
</div>
<div className="module-table-inner">
<div className="module-table-flex-wrap tab2">
<div className="module-flex-item">
<div className="eaves-keraba-table">
{selectedRoof && selectedRoof.lenAuth === 'C' && (
<>
<div className="eaves-keraba-item">
<div className="eaves-keraba-th">L</div>
<div className="eaves-keraba-td">
<div className="grid-select">
<input
type="text"
className="input-origin block"
value={lengthBase}
onChange={(e) => {
const v = e.target.value
if (v === '') {
onChangeLength('')
return
}
const n = Number(normalizeDigits(v))
if (Number.isNaN(n)) {
onChangeLength('')
} else {
onChangeLength(n)
}
}}
disabled={selectedRoof.lenAuth === 'R'}
/>
</div>
</div>
</div>
</>
)}
{selectedRoof && ['C', 'R'].includes(selectedRoof.raftAuth) && (
<>
<div className="eaves-keraba-item">
<div className="eaves-keraba-th">{getMessage('modal.module.basic.setting.module.rafter.margin')}</div>
<div className="eaves-keraba-td">
<div className="grid-select">
{raftBaseList.length > 0 && (
<QSelectBox
options={raftBaseList}
value={selectedRaftBase}
sourceKey={'clCode'}
targetKey={'clCode'}
showKey={'clCodeNm'}
disabled={selectedRoof.raftAuth === 'R'}
onChange={(e) => onChangeRaftBase(e)}
showFirstOptionWhenEmpty={true}
/>
)}
</div>
</div>
</div>
</>
)}
{selectedRoof && ['C', 'R'].includes(selectedRoof.roofPchAuth) && (
<>
<div className="eaves-keraba-item">
<div className="eaves-keraba-th">{getMessage('modal.module.basic.setting.module.hajebichi')}</div>
<div className="eaves-keraba-td">
<div className="grid-select">
<input
type="text"
className="input-origin block"
disabled={selectedRoof.roofPchAuth === 'R'}
onChange={(e) => {
const v = e.target.value
if (v === '') {
onChangeHajebichi('')
return
}
const n = Number(normalizeDigits(v))
if (Number.isNaN(n)) {
onChangeHajebichi('')
} else {
onChangeHajebichi(n)
}
}}
value={hajebichi}
/>
</div>
</div>
</div>
</>
)}
<div className="eaves-keraba-item">
<div className="eaves-keraba-th">{getMessage('modal.module.basic.setting.module.trestle.maker')}</div>
<div className="eaves-keraba-td">
<div className="grid-select">
{trestleList && (
<QSelectBox
title={getMessage('selectbox.title')}
options={trestleList}
value={selectedTrestle}
sourceKey={'trestleMkrCd'}
targetKey={'trestleMkrCd'}
showKey={'trestleMkrCdJp'}
onChange={(e) => onChangeTrestleMaker(e)}
showFirstOptionWhenEmpty={true}
/>
)}
</div>
</div>
</div>
<div className="eaves-keraba-item">
<div className="eaves-keraba-th">{getMessage('modal.module.basic.setting.module.construction.method')}</div>
<div className="eaves-keraba-td">
<div className="grid-select">
{constMthdList && (
<QSelectBox
title={getMessage('selectbox.title')}
options={constMthdList}
value={selectedConstMthd}
sourceKey={'constMthdCd'}
targetKey={'constMthdCd'}
showKey={'constMthdCdJp'}
onChange={(e) => onChangeConstMthd(e)}
showFirstOptionWhenEmpty={true}
/>
)}
</div>
</div>
</div>
<div className="eaves-keraba-item">
<div className="eaves-keraba-th">{getMessage('modal.module.basic.setting.module.under.roof')}</div>
<div className="eaves-keraba-td">
<div className="grid-select">
{roofBaseList && (
<QSelectBox
title={getMessage('selectbox.title')}
options={roofBaseList}
sourceKey={'roofBaseCd'}
targetKey={'roofBaseCd'}
showKey={'roofBaseCdJp'}
value={selectedRoofBase}
onChange={(e) => onChangeRoofBase(e)}
showFirstOptionWhenEmpty={true}
/>
)}
</div>
</div>
</div>
</div>
</div>
<div className="module-flex-item non-flex">
<div className="flex-item-btn-wrap">
<button className={`btn-frame roof ${getConstructionState(0)}`} onClick={() => handleConstruction(0)}>
{getMessage('modal.module.basic.setting.module.standard.construction')}(I)
</button>
<button className={`btn-frame roof ${getConstructionState(3)}`} onClick={() => handleConstruction(3)}>
{getMessage('modal.module.basic.setting.module.multiple.construction')}
</button>
<button className={`btn-frame roof ${getConstructionState(1)}`} onClick={() => handleConstruction(1)}>
{getMessage('modal.module.basic.setting.module.standard.construction')}
</button>
<button className={`btn-frame roof ${getConstructionState(4)}`} onClick={() => handleConstruction(4)}>
{getMessage('modal.module.basic.setting.module.multiple.construction')}(II)
</button>
<button className={`btn-frame roof ${getConstructionState(2)}`} onClick={() => handleConstruction(2)}>
{getMessage('modal.module.basic.setting.module.enforce.construction')}
</button>
</div>
<div className="grid-check-form-flex">
<div className="d-check-box pop">
<input
type="checkbox"
id={`ch01`}
disabled={!cvrYn || cvrYn === 'N'}
checked={cvrChecked || false}
// onChange={() => dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, cvrChecked: !trestleState.cvrChecked } })}
onChange={() => setCvrChecked(!cvrChecked)}
/>
<label htmlFor={`ch01`}>{getMessage('modal.module.basic.setting.module.eaves.bar.fitting')}</label>
</div>
<div className="d-check-box pop">
<input
type="checkbox"
id={`ch02`}
disabled={!snowGdPossYn || snowGdPossYn === 'N'}
checked={snowGdChecked || false}
// onChange={() => dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, snowGdChecked: !trestleState.snowGdChecked } })}
onChange={() => setSnowGdChecked(!snowGdChecked)}
/>
<label htmlFor={`ch02`}>{getMessage('modal.module.basic.setting.module.blind.metal.fitting')}</label>
</div>
</div>
</div>
</div>
<div className="module-input-area">
<div className="module-area-title">{getMessage('modal.module.basic.setting.module.placement.area')}</div>
<div className="module-input-wrap">
<div className="outline-form mr15">
<span>{getMessage('modal.module.basic.setting.module.placement.area.eaves')}</span>
<div className="input-grid mr10">
{/*<input*/}
{/* type="number"*/}
{/* className="input-origin block"*/}
{/* value={eavesMargin ?? 0}*/}
{/* // onChange={(e) => dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, eavesMargin: e.target.value } })}*/}
{/* onChange={(e) => setEavesMargin(+e.target.value)}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={eavesMargin ?? 0}
onChange={(value) => setEavesMargin(value)}
options={{
allowNegative: false,
allowDecimal: false
}}
/>
</div>
<span className="thin">mm</span>
</div>
<div className="outline-form mr15">
<span>{getMessage('modal.module.basic.setting.module.placement.area.ridge')}</span>
<div className="input-grid mr10">
{/*<input*/}
{/* type="number"*/}
{/* className="input-origin block"*/}
{/* value={ridgeMargin ?? 0}*/}
{/* // onChange={(e) => dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, ridgeMargin: e.target.value } })}*/}
{/* onChange={(e) => setRidgeMargin(+e.target.value)}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={ridgeMargin ?? 0}
onChange={(value) => setRidgeMargin(value)}
options={{
allowNegative: false,
allowDecimal: false
}}
/>
</div>
<span className="thin">mm</span>
</div>
<div className="outline-form ">
<span>{getMessage('modal.module.basic.setting.module.placement.area.keraba')}</span>
<div className="input-grid mr10">
{/*<input*/}
{/* type="number"*/}
{/* className="input-origin block"*/}
{/* value={kerabaMargin ?? 0}*/}
{/* // onChange={(e) => dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, kerabaMargin: e.target.value } })}*/}
{/* onChange={(e) => setKerabaMargin(+e.target.value)}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={kerabaMargin ?? 0}
onChange={(value) => setKerabaMargin(value)}
options={{
allowNegative: false,
allowDecimal: false
}}
/>
</div>
<span className="thin">mm</span>
</div>
</div>
</div>
<div className="module-input-area">
<div className="module-area-title">{getMessage('modal.module.basic.setting.module.placement.margin')}</div>
<div className="module-input-wrap">
<div className="outline-form">
<span>{getMessage('modal.module.basic.setting.module.placement.margin.horizontal')}</span>
<div className="input-grid mr10">
<input type="text" className="input-origin block" defaultValue={trestleDetail?.moduleIntvlHor} readOnly />
</div>
<span className="thin">mm</span>
</div>
<div className="outline-form">
<span>{getMessage('modal.module.basic.setting.module.placement.margin.vertical')}</span>
<div className="input-grid mr10">
<input type="text" className="input-origin block" defaultValue={trestleDetail?.moduleIntvlVer} readOnly />
</div>
<span className="thin">mm</span>
</div>
</div>
</div>
</div>
</div>
<div className="module-bottom">
<div className="module-table-box ">
<div className="warning-guide">
<div className="warning">
{getMessage('modal.module.basic.setting.module.setting.info1')}
<br />
{getMessage('modal.module.basic.setting.module.setting.info2')}
</div>
</div>
</div>
</div>
</div>
)
})
export default Trestle

View File

@ -33,14 +33,6 @@ const PitchPlacement = forwardRef((props, refs) => {
setSelectedItems({ ...selectedItems, [e.target.name]: e.target.checked })
}
const moduleData = {
header: [
{ type: 'check', name: '', prop: 'check', width: 70 },
{ type: 'color-box', name: getMessage('module'), prop: 'module' },
{ type: 'text', name: `${getMessage('output')} (W)`, prop: 'output', width: 70 },
],
}
//
useEffect(() => {
const checkedModuleIds = Object.keys(selectedItems).filter((key) => selectedItems[key])
@ -105,24 +97,20 @@ const PitchPlacement = forwardRef((props, refs) => {
return (
<>
<div className="module-table-box mb10">
<div className="hexagonal-flex-wrap">
<div className="module-table-box ">
<div className="module-table-inner">
<div className="roof-module-table">
<table>
<thead>
<tr>
{moduleData.header.map((data) => (
<th key={data.prop} style={{ width: data.width ? data.width : '' }}>
{data.type === 'check' ? (
<th style={{ width: '70px' }}>
<div className="d-check-box no-text pop">
<input type="checkbox" id="ch01" disabled />
<label htmlFor="ch01"></label>
</div>
) : (
data.name
)}
</th>
))}
<th>{getMessage('module')}</th>
</tr>
</thead>
<tbody>
@ -147,7 +135,6 @@ const PitchPlacement = forwardRef((props, refs) => {
<span className="name">{item.itemNm}</span>
</div>
</td>
<td className="al-r">{item.wpOut}</td>
</tr>
))}
</tbody>
@ -155,15 +142,20 @@ const PitchPlacement = forwardRef((props, refs) => {
</div>
</div>
</div>
<div className="module-table-box mb10">
<div className="module-table-box non-flex">
<div className="module-table-inner">
<div className="hexagonal-wrap">
<div className="hexagonal-item">
<div className="bold-font">{getMessage('modal.module.basic.setting.pitch.module.placement.standard.setting')}</div>
</div>
<div className="hexagonal-item">
<div className="pop-form-radio">
<div className="d-check-radio pop">
<div className="roof-module-table">
<table>
<thead>
<tr>
<th>{getMessage('modal.module.basic.setting.pitch.module.placement.standard.setting')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div className="hexagonal-radio-wrap">
<div className="d-check-radio pop mb10">
<input
type="radio"
name="radio01"
@ -186,6 +178,10 @@ const PitchPlacement = forwardRef((props, refs) => {
<label htmlFor="ra02">{getMessage('modal.module.basic.setting.pitch.module.placement.standard.setting.select')}</label>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View File

@ -1,30 +1,28 @@
import WithDraggable from '@/components/common/draggable/WithDraggable'
import { useState, useEffect, useContext, useRef } from 'react'
import { useContext, useEffect, useRef, useState } from 'react'
import PowerConditionalSelect from '@/components/floor-plan/modal/circuitTrestle/step/PowerConditionalSelect'
import StepUp from '@/components/floor-plan/modal/circuitTrestle/step/StepUp'
import { useMessage } from '@/hooks/useMessage'
import { usePopup } from '@/hooks/usePopup'
import PassivityCircuitAllocation from './step/type/PassivityCircuitAllocation'
import BasicSetting from '@/components/floor-plan/modal/basic/BasicSetting'
import { useMasterController } from '@/hooks/common/useMasterController'
import { correntObjectNoState } from '@/store/settingAtom'
import { useRecoilValue } from 'recoil'
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
import { GlobalDataContext } from '@/app/GlobalDataProvider'
import { useRecoilState } from 'recoil'
import { makersState, modelsState, modelState, pcsCheckState, selectedMakerState, selectedModelsState, seriesState } from '@/store/circuitTrestleAtom'
import { POLYGON_TYPE } from '@/common/common'
import { POLYGON_TYPE, MENU } from '@/common/common'
import { useSwal } from '@/hooks/useSwal'
import { canvasState } from '@/store/canvasAtom'
import { canvasState, canvasZoomState, currentMenuState } from '@/store/canvasAtom'
import { useTrestle } from '@/hooks/module/useTrestle'
import { selectedModuleState } from '@/store/selectedModuleOptions'
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
import { v4 as uuidv4 } from 'uuid'
import { useEstimate } from '@/hooks/useEstimate'
import { useCircuitTrestle } from '@/hooks/useCirCuitTrestle'
import { useCanvasPopupStatusController } from '@/hooks/common/useCanvasPopupStatusController'
import { useImgLoader } from '@/hooks/floorPlan/useImgLoader'
import { usePlan } from '@/hooks/usePlan'
import { QcastContext } from '@/app/QcastProvider'
import { fontSelector } from '@/store/fontAtom'
import { fabric } from 'fabric'
const ALLOCATION_TYPE = {
AUTO: 'auto',
@ -32,18 +30,22 @@ const ALLOCATION_TYPE = {
}
export default function CircuitTrestleSetting({ id }) {
const { getMessage } = useMessage()
const { closePopup } = usePopup()
const { closePopup, addPopup } = usePopup()
const { apply, setViewCircuitNumberTexts, getEstimateData, clear: clearTrestle, setAllModuleSurfaceIsComplete } = useTrestle()
const { swalFire } = useSwal()
const { saveEstimate } = useEstimate()
const canvas = useRecoilValue(canvasState)
const setCurrentMenu = useSetRecoilState(currentMenuState)
const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState)
const [tabNum, setTabNum] = useState(1)
const [allocationType, setAllocationType] = useState(ALLOCATION_TYPE.AUTO)
const [circuitAllocationType, setCircuitAllocationType] = useState(1)
const { managementState, setManagementState, managementStateLoaded } = useContext(GlobalDataContext)
const { managementState, setManagementState } = useContext(GlobalDataContext)
const selectedModules = useRecoilValue(selectedModuleState)
const { getPcsAutoRecommendList, getPcsVoltageChk, getPcsVoltageStepUpList, getPcsManualConfChk } = useMasterController()
const flowText = useRecoilValue(fontSelector('flowText'))
const lengthText = useRecoilValue(fontSelector('lengthText'))
const circuitNumberText = useRecoilValue(fontSelector('circuitNumberText'))
// ()
const [selectedStepUpValues, setSelectedStepUpValues] = useState({})
@ -55,10 +57,13 @@ export default function CircuitTrestleSetting({ id }) {
const [seletedSubOption, setSeletedSubOption] = useState(null)
const { setModuleStatisticsData } = useCircuitTrestle()
const { handleCanvasToPng } = useImgLoader()
const moduleSelectionData = useRecoilValue(moduleSelectionDataState)
const passivityCircuitAllocationRef = useRef()
const { setIsGlobalLoading } = useContext(QcastContext)
const originCanvasViewPortTransform = useRef([])
const [isFold, setIsFold] = useState(false)
const {
makers,
setMakers,
@ -83,8 +88,8 @@ export default function CircuitTrestleSetting({ id }) {
} = useCircuitTrestle()
// const { trigger: moduleSelectedDataTrigger } = useCanvasPopupStatusController(2)
useEffect(() => {
originCanvasViewPortTransform.current = [...canvas.viewportTransform]
if (!managementState) {
setManagementState(managementStateLoaded)
}
// setCircuitData({
// makers,
@ -103,6 +108,276 @@ export default function CircuitTrestleSetting({ id }) {
}
}, [])
// rack reopen
useEffect(() => {
const modules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE)
if (modules.length === 0) {
return
}
/**
* 설치 모듈 크기 검증
* - 남쪽: 아래쪽 모듈이 위쪽 모듈보다 커야
* - 북쪽: 위쪽 모듈이 아래쪽 모듈보다 커야
* - 동쪽: 오른쪽 모듈이 왼쪽 모듈보다 커야
* - 서쪽: 왼쪽 모듈이 오른쪽 모듈보다 커야
*/
const validateModuleSizeForRack = (surface) => {
const { modules, direction, trestleDetail } = surface
const { rackYn, moduleIntvlHor, moduleIntvlVer } = trestleDetail
if (rackYn === 'N' || !modules || modules.length < 2) {
return true //
}
//
const centerPoints = modules.map((module) => {
const { x, y } = module.getCenterPoint()
const { width, height } = module
return {
x,
y,
width: Math.floor(width),
height: Math.floor(height),
area: Math.floor(width) * Math.floor(height),
}
})
//
const isVertical = direction === 'south' || direction === 'north'
const primaryInterval = isVertical ? moduleIntvlVer : moduleIntvlHor
const secondaryInterval = isVertical ? moduleIntvlHor : moduleIntvlVer
// :
const getSortFn = () => {
switch (direction) {
case 'south': return (a, b) => b.y - a.y // (y )
case 'north': return (a, b) => a.y - b.y // (y )
case 'east': return (a, b) => b.x - a.x // (x )
case 'west': return (a, b) => a.x - b.x // (x )
default: return () => 0
}
}
// ( )
const findTargetModules = (current, margin) => {
return centerPoints.filter(cp => {
const sameAxis = isVertical
? Math.abs(cp.x - current.x) < margin
: Math.abs(cp.y - current.y) < margin
const targetDirection = direction === 'south' ? cp.y < current.y
: direction === 'north' ? cp.y > current.y
: direction === 'east' ? cp.x < current.x
: cp.x > current.x
return sameAxis && targetDirection
})
}
//
const getClosestTarget = (filtered) => {
if (filtered.length === 0) return null
return filtered.reduce((closest, cp) => {
switch (direction) {
case 'south': return cp.y > closest.y ? cp : closest
case 'north': return cp.y < closest.y ? cp : closest
case 'east': return cp.x > closest.x ? cp : closest
case 'west': return cp.x < closest.x ? cp : closest
default: return closest
}
})
}
//
const getGap = (current, target) => {
if (isVertical) {
return direction === 'south'
? (current.y - current.height / 2) - (target.y + target.height / 2)
: (target.y - target.height / 2) - (current.y + current.height / 2)
} else {
return direction === 'east'
? (current.x - current.width / 2) - (target.x + target.width / 2)
: (target.x - target.width / 2) - (current.x + current.width / 2)
}
}
//
const isAdjacent = (current, target) => {
const gap = getGap(current, target)
return gap >= 0 && gap <= primaryInterval + 1
}
//
const sortedPoints = [...centerPoints].sort(getSortFn())
for (const current of sortedPoints) {
// 1. :
const directTargets = findTargetModules(current, secondaryInterval)
const closestTarget = getClosestTarget(directTargets)
if (closestTarget && isAdjacent(current, closestTarget) && closestTarget.area > current.area) {
return false //
}
// 2. :
const size = isVertical ? current.width : current.height
const halfOffset = (size + secondaryInterval) / 2
for (const sign of [-1, 1]) {
const offsetValue = sign * halfOffset
const findHalfTarget = centerPoints.filter((cp) => {
const offsetAxis = isVertical
? Math.abs(cp.x - (current.x + offsetValue)) <= primaryInterval
: Math.abs(cp.y - (current.y + offsetValue)) <= primaryInterval
const targetDirection = direction === 'south' ? cp.y < current.y
: direction === 'north' ? cp.y > current.y
: direction === 'east' ? cp.x < current.x
: cp.x > current.x
return offsetAxis && targetDirection
})
const closestHalf = getClosestTarget(findHalfTarget)
if (closestHalf && isAdjacent(current, closestHalf) && closestHalf.area > current.area) {
return false //
}
}
}
return true //
}
//
const moduleSetupSurfaces = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE)
for (const surface of moduleSetupSurfaces) {
if (!validateModuleSizeForRack(surface)) {
swalFire({
text: getMessage('module.size.validation.rack.error'),
icon: 'error',
confirmFn: () => {
//
closePopup(id)
//
setCurrentMenu(MENU.MODULE_CIRCUIT_SETTING.BASIC_SETTING)
// /
const newPopupId = uuidv4()
clearTrestle()
setAllModuleSurfaceIsComplete(false)
addPopup(newPopupId, 1, <BasicSetting id={newPopupId} />)
},
})
return
}
}
}, [])
const capture = async (type) => {
beforeCapture(type)
await handleCanvasToPng(type)
afterCapture(type)
return new Promise((resolve) => {
setTimeout(() => {
resolve(true)
}, 1000)
})
}
//
const beforeCapture = (type) => {
setCanvasZoom(100)
canvas.set({ zoom: 1 })
// roof
const roofs = canvas.getObjects().filter((obj) => obj.name === 'roof' && !obj.wall)
if (roofs.length > 0) {
// roof x, y
const allPoints = []
roofs.forEach((roof) => {
if (roof.getCurrentPoints()) {
roof.getCurrentPoints().forEach((point) => {
allPoints.push({ x: point.x, y: point.y })
})
}
})
if (allPoints.length > 0) {
//
const minX = Math.min(...allPoints.map((p) => p.x))
const maxX = Math.max(...allPoints.map((p) => p.x))
const minY = Math.min(...allPoints.map((p) => p.y))
const maxY = Math.max(...allPoints.map((p) => p.y))
const centerX = (minX + maxX) / 2
const centerY = (minY + maxY) / 2
//
const canvasWidth = canvas.getWidth()
const canvasHeight = canvas.getHeight()
const offsetX = canvasWidth / 2 - centerX
const offsetY = canvasHeight / 2 - centerY
canvas.viewportTransform = [1, 0, 0, 1, offsetX, offsetY]
} else {
canvas.viewportTransform = [1, 0, 0, 1, 0, 0]
}
} else {
canvas.viewportTransform = [1, 0, 0, 1, 0, 0]
}
const modules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE)
const circuitNumberTexts = canvas.getObjects().filter((obj) => obj.name === 'circuitNumber')
if (type === 2) {
modules.forEach((module) => {
module.set({ originColor: module.fill, fill: null, strokeWidth: 2 })
})
circuitNumberTexts.forEach((text) => {
text.set({ visible: false })
})
}
let x, y
x = canvas.width / 2
y = canvas.height / 2
canvas.zoomToPoint(new fabric.Point(x, y), 0.4)
changeFontSize('lengthText', '28')
changeFontSize('circuitNumber', '28')
changeFontSize('flowText', '28')
canvas.renderAll()
}
//
const afterCapture = (type) => {
if (originCanvasViewPortTransform.current[0] !== 1) {
setCanvasZoom(Number((originCanvasViewPortTransform.current[0] * 100).toFixed(0)))
}
canvas.viewportTransform = [...originCanvasViewPortTransform.current]
canvas.renderAll()
changeFontSize('lengthText', lengthText.fontSize.value)
changeFontSize('circuitNumber', circuitNumberText.fontSize.value)
changeFontSize('flowText', flowText.fontSize.value)
const modules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE)
const circuitNumberTexts = canvas.getObjects().filter((obj) => obj.name === 'circuitNumber')
if (type === 2) {
modules.forEach((module) => {
module.set({
fill: module.originColor,
strokeWidth: 0.3,
})
})
circuitNumberTexts.forEach((text) => {
text.set({ visible: true })
})
}
canvas.renderAll()
}
//
// PCS
@ -115,11 +390,33 @@ export default function CircuitTrestleSetting({ id }) {
return
}
const isMultiModule = selectedModules.itemList.length > 1
let isAllIndfcs = false
if (isMultiModule) {
//INDFCS , OUTDMULTI
// 1. pcs alert
if (selectedModels.length > 0) {
isAllIndfcs = selectedModels.every((model) => model.pcsTpCd === 'INDFCS')
} else {
isAllIndfcs = models.every((model) => model.pcsTpCd === 'INDFCS')
}
}
if (isAllIndfcs) {
swalFire({
title: getMessage('module.circuit.indoor.focused.error'),
type: 'alert',
})
return
}
const params = {
...getOptYn(),
useModuleItemList: getUseModuleItemList(),
roofSurfaceList: getRoofSurfaceList(),
pcsItemList: getPcsItemList(),
pcsItemList: getPcsItemList(isMultiModule),
}
//
@ -155,6 +452,7 @@ export default function CircuitTrestleSetting({ id }) {
getPcsVoltageChk(pcsVoltageChkParams).then((res) => {
if (res.resultCode === 'S') {
setTabNum(2)
setAllModuleSurfaceIsComplete(false)
} else {
swalFire({
title: res.resultMsg,
@ -179,15 +477,16 @@ export default function CircuitTrestleSetting({ id }) {
})
} else {
//
getPcsVoltageChk({ ...params, pcsItemList: getSelectedPcsItemList() }).then((res) => {
getPcsVoltageChk({ ...params, pcsItemList: getSelectedPcsItemList(isMultiModule) }).then((res) => {
if (res.resultCode === 'S') {
//
getPcsVoltageStepUpList({
...params,
pcsItemList: getSelectedPcsItemList(),
pcsItemList: getSelectedPcsItemList(isMultiModule),
}).then((res) => {
if (res?.result.resultCode === 'S' && res?.data) {
setTabNum(2)
setAllModuleSurfaceIsComplete(false)
} else {
swalFire({ text: getMessage('common.message.send.error') })
}
@ -287,6 +586,8 @@ export default function CircuitTrestleSetting({ id }) {
setSelectedModels(pcsItemList)
getPcsVoltageChk(pcsVoltageChkParams).then((res) => {
setAllocationType(ALLOCATION_TYPE.PASSIVITY)
setAllModuleSurfaceIsComplete(false)
clearTrestle()
})
} else {
swalFire({
@ -309,8 +610,15 @@ export default function CircuitTrestleSetting({ id }) {
const target = pcsCheck.max ? moduleMaxQty : moduleStdQty
const placementModules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE)
let moduleAmount = placementModules.reduce((acc, module) => {
if (moduleSelectionData.module.itemList.length === 1 || module.moduleInfo.itemId === moduleSelectionData.module.itemList[0].itemId) {
return acc + 1
} else {
return acc + 0.66
}
}, 0)
if (placementModules.length > target) {
if (moduleAmount > target) {
swalFire({
title: getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.all.power.conditional.validation.error01'),
type: 'alert',
@ -319,6 +627,7 @@ export default function CircuitTrestleSetting({ id }) {
}
setAllocationType(ALLOCATION_TYPE.PASSIVITY)
clearTrestle()
}
}
@ -348,13 +657,9 @@ export default function CircuitTrestleSetting({ id }) {
.map((obj) => {
obj.pcses = getStepUpListData()
})
setViewCircuitNumberTexts(false)
handleCanvasToPng(1)
await capture(1)
// result=null
setViewCircuitNumberTexts(true)
//
//
@ -369,7 +674,7 @@ export default function CircuitTrestleSetting({ id }) {
const result = await getEstimateData()
if (result) {
handleCanvasToPng(2)
await capture(2)
//
await saveEstimate(result)
} else {
@ -379,6 +684,16 @@ export default function CircuitTrestleSetting({ id }) {
// removeNotAllocationModules()
}
const changeFontSize = (name, size) => {
const textObjs = canvas?.getObjects().filter((obj) => obj.name === name)
textObjs.forEach((obj) => {
obj.set({
fontSize: size,
})
})
canvas.renderAll()
}
//
const onClickPrev = () => {
// setAllocationType(ALLOCATION_TYPE.AUTO)
@ -393,6 +708,7 @@ export default function CircuitTrestleSetting({ id }) {
obj.circuit = null
obj.pcsItemId = null
obj.circuitNumber = null
obj.pcs = null
})
setSelectedModels(
JSON.parse(JSON.stringify(selectedModels)).map((model) => {
@ -479,7 +795,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,
@ -655,14 +971,22 @@ export default function CircuitTrestleSetting({ id }) {
return
} else {
setTabNum(2)
setAllModuleSurfaceIsComplete(false)
}
})
}
return (
<WithDraggable isShow={true} pos={{ x: 50, y: 230 }} className="l-2">
<WithDraggable.Header title={getMessage('modal.circuit.trestle.setting')} onClose={() => handleClose()} />
<WithDraggable.Header
title={getMessage('modal.circuit.trestle.setting')}
onClose={() => handleClose()}
isFold={isFold}
onFold={() => setIsFold(!isFold)}
/>
<WithDraggable.Body>
<div style={{ display: !(tabNum === 1 && allocationType === ALLOCATION_TYPE.PASSIVITY) && isFold ? 'none' : 'block' }}>
<div style={{ display: tabNum === 1 && allocationType === ALLOCATION_TYPE.PASSIVITY && isFold ? 'none' : 'block' }}>
<div className="roof-module-tab">
<div className={`module-tab-bx act`}>{getMessage('modal.circuit.trestle.setting.power.conditional.select')}</div>
<span className={`tab-arr ${tabNum === 2 ? 'act' : ''}`}></span>
@ -670,11 +994,14 @@ export default function CircuitTrestleSetting({ id }) {
{getMessage('modal.circuit.trestle.setting.circuit.allocation')}({getMessage('modal.circuit.trestle.setting.step.up.allocation')})
</div>
</div>
</div>
{tabNum === 1 && allocationType === ALLOCATION_TYPE.AUTO && <PowerConditionalSelect {...powerConditionalSelectProps} />}
{tabNum === 1 && allocationType === ALLOCATION_TYPE.PASSIVITY && (
<PassivityCircuitAllocation {...passivityProps} ref={passivityCircuitAllocationRef} />
<PassivityCircuitAllocation {...passivityProps} ref={passivityCircuitAllocationRef} isFold={isFold} />
)}
{tabNum === 2 && <StepUp {...stepUpProps} onInitialize={handleStepUpInitialize} />}
</div>
{tabNum === 1 && allocationType === ALLOCATION_TYPE.AUTO && (
<div className="grid-btn-wrap">
<button className="btn-frame modal mr5 act" onClick={() => onAutoRecommend()}>

View File

@ -5,6 +5,7 @@ import { useMasterController } from '@/hooks/common/useMasterController'
import { useMessage } from '@/hooks/useMessage'
import { useSwal } from '@/hooks/useSwal'
import { pcsCheckState } from '@/store/circuitTrestleAtom'
import { sessionStore } from '@/store/commonAtom'
import { globalLocaleStore } from '@/store/localeAtom'
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
import { isNullOrUndefined } from '@/util/common-utils'
@ -38,13 +39,14 @@ export default function PowerConditionalSelect(props) {
} = props
const [pcsCheck, setPcsCheck] = useRecoilState(pcsCheckState)
const sessionState = useRecoilValue(sessionStore)
const { getMessage } = useMessage()
const [selectedRow, setSelectedRow] = useState(null)
const globalLocale = useRecoilValue(globalLocaleStore)
const { getPcsMakerList, getPcsModelList } = useMasterController()
const selectedModules = useRecoilValue(selectedModuleState)
const { swalFire } = useSwal()
const { trigger: moduleSelectedDataTrigger } = useCanvasPopupStatusController(2)
// const { trigger: moduleSelectedDataTrigger } = useCanvasPopupStatusController(2)
const [moduleSelectionData, setModuleSelectionData] = useRecoilState(moduleSelectionDataState)
const modelHeader = [
{ name: getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.series'), width: '15%', prop: 'pcsSerNm', type: 'color-box' },
@ -72,11 +74,9 @@ export default function PowerConditionalSelect(props) {
]
useEffect(() => {
if (makers.length === 0) {
getPcsMakerList().then((res) => {
setMakers(res.data)
})
}
}, [])
const onCheckSeries = (data) => {
@ -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))
@ -131,7 +132,7 @@ export default function PowerConditionalSelect(props) {
mixMatlNo: item.mixMatlNo,
}
})
getPcsModelList({ pcsMkrCd, pcsSerList, moduleItemList }).then((res) => {
getPcsModelList({ pcsMkrCd, pcsSerList, moduleItemList, storeId: sessionState.oneTwoStoreId }).then((res) => {
if (res?.result.code === 200 && res?.data) {
setModels(
res.data.map((model) => {
@ -176,7 +177,7 @@ export default function PowerConditionalSelect(props) {
if (selectedMaker.pcsMkrMultiType === PCS_MKR_MULTI_TYPE.MULTI) {
setSelectedModels([...selectedModels, { ...selectedRow, id: uuidv4() }])
} else if (!selectedModels.find((m) => m.itemId === selectedRow.itemId)) {
} else if (!selectedModels.find((m) => m.itemId === selectedRow.itemId && m.pcsSerCd === selectedRow.pcsSerCd)) {
setSelectedModels([...selectedModels, { ...selectedRow, id: uuidv4() }])
}
setSelectedRow(null)
@ -199,6 +200,8 @@ export default function PowerConditionalSelect(props) {
const param = {
pcsMkrCd: option.pcsMkrCd,
mixMatlNo: moduleSelectionData.module.mixMatlNo,
moduleMatlCds: moduleSelectionData.module.itemList.map((item) => item.itemId).join(','),
storeId: sessionState.oneTwoStoreId,
}
getPcsMakerList(param).then((res) => {

View File

@ -1,13 +1,10 @@
import { GlobalDataContext } from '@/app/GlobalDataProvider'
import QSelectBox from '@/components/common/select/QSelectBox'
import { useMessage } from '@/hooks/useMessage'
import { canvasState } from '@/store/canvasAtom'
import { modelState, pcsCheckState } from '@/store/circuitTrestleAtom'
import { pcsCheckState } from '@/store/circuitTrestleAtom'
import { selectedModuleState } from '@/store/selectedModuleOptions'
import { useContext, useEffect, useState } from 'react'
import { useEffect, useState } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import { useCanvasPopupStatusController } from '@/hooks/common/useCanvasPopupStatusController'
import { canvasPopupStatusStore } from '@/store/canvasPopupStatusAtom'
import { useMasterController } from '@/hooks/common/useMasterController'
import { v4 as uuidv4 } from 'uuid'
import { globalLocaleStore } from '@/store/localeAtom'
@ -15,7 +12,6 @@ import { POLYGON_TYPE } from '@/common/common'
import { useSwal } from '@/hooks/useSwal'
import { circuitNumDisplaySelector } from '@/store/settingAtom'
import { fontSelector } from '@/store/fontAtom'
import { PCS_MKR_MULTI_TYPE } from './PowerConditionalSelect'
export default function StepUp(props) {
const {
@ -42,7 +38,7 @@ export default function StepUp(props) {
const [arrayLength, setArrayLength] = useState(3) //module-table-inner
const [pcsCheck, setPcsCheck] = useRecoilState(pcsCheckState)
const { getPcsVoltageStepUpList, getPcsAutoRecommendList, getPcsVoltageChk, getPcsConnOptionItemList } = useMasterController()
const { managementState, setManagementState, managementStateLoaded } = useContext(GlobalDataContext)
const [originPcsVoltageStepUpList, setOriginPcsVoltageStepUpList] = useState([])
const canvas = useRecoilValue(canvasState)
const selectedModules = useRecoilValue(selectedModuleState)
const [optCodes, setOptCodes] = useState([])
@ -101,6 +97,9 @@ export default function StepUp(props) {
/** PCS 승압설정 정보 SET */
setStepUpListData(stepUpListData)
if (originPcsVoltageStepUpList.length === 0) {
setOriginPcsVoltageStepUpList(stepUpListData)
}
/** PCS 옵션 조회 */
// const formattedOptCodes = formatOptionCodes(res.data.optionList)
@ -109,7 +108,7 @@ export default function StepUp(props) {
/** 캔버스에 회로 정보 적용 */
// pcs setSubOpsions, setMainOptions
console.log('stepUpListData', stepUpListData)
let mChk = 0
stepUpListData[0].pcsItemList.forEach((pcsItem, index) => {
const optionList = formatOptionCodes(pcsItem.optionList)
if (isMultiOptions()) {
@ -165,6 +164,8 @@ export default function StepUp(props) {
targetModule.pcsItemId = module.pcsItemId
targetModule.circuitNumber = module.circuit
canvas.add(moduleCircuitText)
} else {
mChk++
}
})
})
@ -173,6 +174,10 @@ export default function StepUp(props) {
canvas.renderAll()
setModuleStatisticsData()
if (mChk > 0) {
swalFire({ text: getMessage('modal.circuit.trestle.setting.step.up.allocation.module.over.count') })
}
} else {
swalFire({ text: getMessage('common.message.send.error') })
}
@ -393,10 +398,14 @@ export default function StepUp(props) {
}))
}
const handleChangeApplyParalQty = (mainIdx, subIdx, applyParalQty) => {
handleRowClick(mainIdx, subIdx, applyParalQty)
}
/**
* 선택 핸들러 함수 추가
*/
const handleRowClick = (mainIdx, subIdx) => {
const handleRowClick = (mainIdx, subIdx, applyParalQty = null) => {
/** 자동 승압 설정인 경우만 실행 */
if (allocationType !== 'auto') return
@ -428,7 +437,13 @@ export default function StepUp(props) {
/** 선택된 serQty 찾기 */
const selectedSerQty = matchingPcsItem?.serQtyList.find((serQty) => serQty.selected)?.serQty || 0
if (index === 0) {
return {
...pcsItem,
applySerQty: selectedSerQty,
applyParalQty: +applyParalQty,
}
}
return {
...pcsItem,
applySerQty: selectedSerQty,
@ -573,7 +588,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 +601,7 @@ export default function StepUp(props) {
value={seletedSubOption}
sourceKey="code"
targetKey="code"
showKey="name"
showKey={`${globalLocale === 'ja' ? 'nameJp' : 'name'}`}
onChange={(e) => setSeletedSubOption(e)}
/>
)}
@ -639,11 +654,51 @@ export default function StepUp(props) {
<tr
key={`row-${serQtyIdx}`}
className={`${item.selected ? 'on' : ''}`}
onClick={() => handleRowClick(idx, serQtyIdx)}
style={{ cursor: allocationType === 'auto' ? 'pointer' : 'default' }}
>
<td className="al-r">{item.serQty}</td>
<td className="al-r">{item.paralQty}</td>
<td
className="al-r"
onClick={() => {
handleRowClick(idx, serQtyIdx, item.paralQty)
}}
>
{item.serQty}
</td>
<td className="al-r">
{/* 2025.12.04 select 추가 */}
{idx === 0 ? (
<select
className="select-light dark table-select"
defaultValue={item.paralQty}
name=""
id=""
onChange={(e) => {
handleChangeApplyParalQty(idx, serQtyIdx, e.target.value)
}}
>
{item.paralQty === 0 && (
<option key="0" value="0">
0
</option>
)}
{Array.from(
{
length: originPcsVoltageStepUpList[index]
? originPcsVoltageStepUpList[index]?.pcsItemList[idx].serQtyList[serQtyIdx].paralQty
: item.paralQty,
},
(_, i) => i + 1,
).map((num) => (
<option key={num} value={num}>
{num}
</option>
))}
</select>
) : (
<>{item.paralQty}</>
)}
</td>
{/* <td className="al-r">{item.paralQty}</td> */}
</tr>
)
})}

View File

@ -1,7 +1,6 @@
import { GlobalDataContext } from '@/app/GlobalDataProvider'
import { POLYGON_TYPE } from '@/common/common'
import { useMasterController } from '@/hooks/common/useMasterController'
import { useModule } from '@/hooks/module/useModule'
import { useCircuitTrestle } from '@/hooks/useCirCuitTrestle'
import { useMessage } from '@/hooks/useMessage'
import { useSwal } from '@/hooks/useSwal'
@ -10,8 +9,9 @@ import { moduleStatisticsState } from '@/store/circuitTrestleAtom'
import { fontSelector } from '@/store/fontAtom'
import { selectedModuleState } from '@/store/selectedModuleOptions'
import { circuitNumDisplaySelector } from '@/store/settingAtom'
import { useContext, useEffect, useState } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import { useContext, useEffect, useRef, useState } from 'react'
import { useRecoilValue } from 'recoil'
import { normalizeDigits } from '@/util/input-utils'
export default function PassivityCircuitAllocation(props) {
const {
@ -21,16 +21,18 @@ export default function PassivityCircuitAllocation(props) {
getOptYn: getApiProps,
getUseModuleItemList: getSelectedModuleList,
getSelectModelList: getSelectModelList,
isFold,
} = props
const { swalFire } = useSwal()
const { getMessage } = useMessage()
const canvas = useRecoilValue(canvasState)
const { managementState, setManagementState, managementStateLoaded } = useContext(GlobalDataContext)
const { managementState } = useContext(GlobalDataContext)
const selectedModules = useRecoilValue(selectedModuleState)
const [selectedPcs, setSelectedPcs] = useState(selectedModels[0])
const { header, rows, footer } = useRecoilValue(moduleStatisticsState)
const [circuitNumber, setCircuitNumber] = useState(1)
const [targetModules, setTargetModules] = useState([])
const targetModulesRef = useRef([])
const { getPcsManualConfChk } = useMasterController()
const isDisplayCircuitNumber = useRecoilValue(circuitNumDisplaySelector)
const { setModuleStatisticsData } = useCircuitTrestle()
@ -38,12 +40,12 @@ export default function PassivityCircuitAllocation(props) {
useEffect(() => {
setModuleStatisticsData()
if (!managementState) {
setManagementState(managementStateLoaded)
}
canvas
.getObjects()
.filter((obj) => obj.name === POLYGON_TYPE.MODULE)
.forEach((obj) => {
obj.set({ pcs: null })
obj.on('mousedown', (e) => handleTargetModules(obj))
})
@ -59,6 +61,10 @@ export default function PassivityCircuitAllocation(props) {
}
}, [])
useEffect(() => {
targetModulesRef.current = targetModules
}, [targetModules])
const handleTargetModules = (obj) => {
if (!Array.isArray(targetModules)) {
setTargetModules([])
@ -79,6 +85,7 @@ export default function PassivityCircuitAllocation(props) {
}
const handleCircuitNumberFix = () => {
const pcsTpCd = selectedPcs.pcsTpCd // ,
let uniqueCircuitNumbers = [
...new Set(
canvas
@ -87,6 +94,16 @@ export default function PassivityCircuitAllocation(props) {
.map((obj) => obj.circuitNumber),
),
]
const surfaceList = targetModules.map((module) => {
return canvas.getObjects().filter((obj) => obj.id === canvas.getObjects().filter((obj) => obj.id === module)[0].surfaceId)[0]
})
let surfaceType = {}
surfaceList.forEach((surface) => {
surfaceType[`${surface.direction}-${surface.roofMaterial.pitch}`] = surface
})
if (!circuitNumber || circuitNumber === 0) {
swalFire({
text: getMessage('module.circuit.minimun.error'),
@ -94,14 +111,69 @@ export default function PassivityCircuitAllocation(props) {
icon: 'warning',
})
return
} else if (targetModules.length === 0) {
}
if (targetModules.length === 0) {
swalFire({
text: getMessage('module.not.found'),
type: 'alert',
icon: 'warning',
})
return
} else if (selectedModels.length > 1) {
}
// targetModule Y N .
const targetModuleGroup = [
...new Set(
canvas
.getObjects()
.filter((obj) => obj.name === POLYGON_TYPE.MODULE && targetModules.includes(obj.id))
.map((obj) => obj.moduleInfo.northModuleYn),
),
]
if (targetModuleGroup.length > 1) {
swalFire({
text: getMessage('module.circuit.fix.not.same.roof.error'),
type: 'alert',
icon: 'warning',
})
return
}
switch (pcsTpCd) {
case 'INDFCS': {
const originHaveThisPcsModules = canvas
.getObjects()
.filter((obj) => obj.name === POLYGON_TYPE.MODULE && obj.pcs && obj.pcs.id === selectedPcs.id)
// 1. ,
const targetModuleInfos = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE && targetModules.includes(obj.id))
debugger
const newTargetModuleGroup = [...new Set(targetModuleInfos.concat(originHaveThisPcsModules).map((obj) => obj.moduleInfo.northModuleYn))]
if (newTargetModuleGroup.length > 1) {
swalFire({
text: getMessage('module.circuit.fix.not.same.roof.error'),
type: 'alert',
icon: 'warning',
})
return
}
break
}
case 'OUTDMULTI': {
if (surfaceList.length > 1) {
if (Object.keys(surfaceType).length > 1) {
swalFire({
text: getMessage('module.circuit.fix.not.same.roof.error'),
type: 'alert',
icon: 'warning',
})
return
}
}
if (selectedModels.length > 1) {
let result = false
uniqueCircuitNumbers.forEach((number) => {
@ -121,6 +193,8 @@ export default function PassivityCircuitAllocation(props) {
return
}
}
}
}
let tempSelectedPcs = { ...selectedPcs }
canvas.discardActiveObject()
@ -169,6 +243,8 @@ export default function PassivityCircuitAllocation(props) {
roofSurfaceId: surface.id,
roofSurface: surface.direction,
roofSurfaceIncl: +canvas.getObjects().filter((obj) => obj.id === surface.parentId)[0].pitch,
roofSurfaceNorthYn: surface.direction === 'north' ? 'Y' : 'N',
roofSurfaceNorthModuleYn: surface.northModuleYn,
moduleList: surface.modules.map((module) => {
return {
itemId: module.moduleInfo.itemId,
@ -250,6 +326,12 @@ export default function PassivityCircuitAllocation(props) {
return
}
targetModules.forEach((module) => {
const modules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE)
const targetModule = modules.find((obj) => obj.id === module)
targetModule.pcs = selectedPcs
})
setTargetModules([])
setCircuitNumber(+circuitNumber + 1)
setModuleStatisticsData()
@ -284,6 +366,7 @@ export default function PassivityCircuitAllocation(props) {
obj.circuit = null
obj.circuitNumber = null
obj.pcsItemId = null
obj.pcs = null
})
setCircuitNumber(minCircuitNumber)
setTargetModules([])
@ -310,6 +393,7 @@ export default function PassivityCircuitAllocation(props) {
obj.circuit = null
obj.circuitNumber = null
obj.pcsItemId = null
obj.pcs = null
})
canvas.renderAll()
setCircuitNumber(1)
@ -477,6 +561,7 @@ export default function PassivityCircuitAllocation(props) {
return (
<>
<div className="properties-setting-wrap outer">
<div style={{ display: isFold ? 'none' : 'block' }}>
<div className="setting-tit">{getMessage('modal.circuit.trestle.setting.circuit.allocation')}</div>
<div className="module-table-box mb10">
<div className="module-table-inner">
@ -521,7 +606,9 @@ export default function PassivityCircuitAllocation(props) {
<div className="module-table-inner">
<div className="hexagonal-wrap">
<div className="hexagonal-item">
<div className="bold-font">{getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.selected.power.conditional')}</div>
<div className="bold-font">
{getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.selected.power.conditional')}
</div>
</div>
<div className="hexagonal-item">
{selectedModels.map((model, index) => (
@ -548,6 +635,7 @@ export default function PassivityCircuitAllocation(props) {
</div>
</div>
</div>
</div>
<div className="slope-wrap">
<div className="circuit-right-wrap mb15">
<div className="outline-form">
@ -561,7 +649,20 @@ export default function PassivityCircuitAllocation(props) {
value={circuitNumber}
min={1}
max={99}
onChange={(e) => setCircuitNumber(e.target.value)}
onChange={(e) => {
const v = e.target.value
if (v === '') {
setCircuitNumber('')
return
}
const n = Number(normalizeDigits(v))
if (Number.isNaN(n)) {
setCircuitNumber('')
} else {
const clamped = Math.max(1, Math.min(99, n))
setCircuitNumber(clamped)
}
}}
/>
</div>
<button className="btn-frame roof" onClick={() => handleCircuitNumberFix()}>

View File

@ -10,6 +10,7 @@ import { defaultSlope } from '@/store/commonAtom'
import { re } from 'mathjs'
import { basicSettingState } from '@/store/settingAtom'
import { getChonByDegree, getDegreeByChon } from '@/util/canvas-util'
import { normalizeDigits } from '@/util/input-utils'
export default function DimensionLineSetting(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
@ -144,8 +145,13 @@ export default function DimensionLineSetting(props) {
defaultValue={''}
value={changeLength}
onChange={(e) => {
console.log(e.target)
setChangeLength(e.target.value)
const v = e.target.value
if (v === '') {
setChangeLength('')
return
}
const n = Number(normalizeDigits(v))
setChangeLength(Number.isNaN(n) ? '' : n)
}}
/>
</div>

View File

@ -3,6 +3,8 @@ import Image from 'next/image'
import { useState } from 'react'
import { useRecoilValue } from 'recoil'
import { ANGLE_TYPE, currentAngleTypeSelector } from '@/store/canvasAtom'
import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function Eaves({ pitchRef, offsetRef, widthRef, radioTypeRef, pitchText }) {
const { getMessage } = useMessage()
@ -20,7 +22,32 @@ export default function Eaves({ pitchRef, offsetRef, widthRef, radioTypeRef, pit
{getMessage('slope')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" defaultValue={currentAngleType === ANGLE_TYPE.SLOPE ? 4 : 21.8} ref={pitchRef} />
{/*<input*/}
{/* type="text"*/}
{/* className="input-origin block"*/}
{/* defaultValue={currentAngleType === ANGLE_TYPE.SLOPE ? 4 : 21.8}*/}
{/* ref={pitchRef}*/}
{/* onChange={(e) => {*/}
{/* const v = normalizeDecimalLimit(e.target.value, 2)*/}
{/* e.target.value = v*/}
{/* if (pitchRef?.current) pitchRef.current.value = v*/}
{/* }}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
ref={pitchRef}
value={currentAngleType === ANGLE_TYPE.SLOPE ? 4 : 21.8}
onChange={(value) => {
if (pitchRef?.current) pitchRef.current.value = value
}}
options={{
allowNegative: false,
allowDecimal: true //(index !== 0),
}}
></CalculatorInput>
</div>
<span className="thin">{pitchText}</span>
</div>
@ -29,7 +56,32 @@ export default function Eaves({ pitchRef, offsetRef, widthRef, radioTypeRef, pit
{getMessage('offset')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" defaultValue={500} ref={offsetRef} />
{/*<input*/}
{/* type="text"*/}
{/* className="input-origin block"*/}
{/* defaultValue={500}*/}
{/* ref={offsetRef}*/}
{/* onChange={(e) => {*/}
{/* const v = normalizeDigits(e.target.value)*/}
{/* e.target.value = v*/}
{/* if (offsetRef?.current) offsetRef.current.value = v*/}
{/* }}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
ref={offsetRef}
value={500}
onChange={(value) => {
if (offsetRef?.current) offsetRef.current.value = value
}}
options={{
allowNegative: false,
allowDecimal: false //(index !== 0),
}}
></CalculatorInput>
</div>
<span className="thin">mm</span>
</div>
@ -45,7 +97,9 @@ export default function Eaves({ pitchRef, offsetRef, widthRef, radioTypeRef, pit
</div>
<div className="eaves-keraba-td">
<div className={`eaves-keraba-ico ${type === '1' ? 'act' : ''}`}>
<Image src="/static/images/canvas/eaves_icon01.svg" alt="react" width={30} height={30} />
<div style={{ width: 30, height: 30, position: 'relative' }}>
<Image src="/static/images/canvas/eaves_icon01.svg" alt="react" fill style={{ objectFit: 'contain' }} />
</div>
</div>
</div>
</div>
@ -58,7 +112,9 @@ export default function Eaves({ pitchRef, offsetRef, widthRef, radioTypeRef, pit
</div>
<div className="eaves-keraba-td">
<div className={`eaves-keraba-ico ${type === '2' ? 'act' : ''}`}>
<Image src="/static/images/canvas/eaves_icon02.svg" alt="react" width={30} height={30} />
<div style={{ width: 30, height: 30, position: 'relative' }}>
<Image src="/static/images/canvas/eaves_icon02.svg" alt="react" fill style={{ objectFit: 'contain' }} />
</div>
</div>
</div>
</div>
@ -66,14 +122,42 @@ export default function Eaves({ pitchRef, offsetRef, widthRef, radioTypeRef, pit
<div className="eaves-keraba-th">
<div className="outline-form">
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="number" min={0} className="input-origin block" defaultValue={500} ref={widthRef} readOnly={type === '1'} />
{/*<input*/}
{/* type="text"*/}
{/* className="input-origin block"*/}
{/* defaultValue={500}*/}
{/* ref={widthRef}*/}
{/* readOnly={type === '1'}*/}
{/* onChange={(e) => {*/}
{/* const v = normalizeDigits(e.target.value)*/}
{/* e.target.value = v*/}
{/* if (widthRef?.current) widthRef.current.value = v*/}
{/* }}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
ref={widthRef}
value={500}
onChange={(value) => {
if (widthRef?.current) widthRef.current.value = value
}}
options={{
allowNegative: false,
allowDecimal: false //(index !== 0),
}}
></CalculatorInput>
</div>
<span className="thin">mm</span>
</div>
</div>
<div className="eaves-keraba-td">
<div className="eaves-keraba-ico ">
<Image src="/static/images/canvas/eaves_icon03.svg" alt="react" width={30} height={30} />
<div style={{ width: 30, height: 30, position: 'relative' }}>
<Image src="/static/images/canvas/eaves_icon03.svg" alt="react" fill style={{ objectFit: 'contain' }} />
</div>
</div>
</div>
</div>

View File

@ -3,6 +3,7 @@ import Image from 'next/image'
import { useState } from 'react'
import { useRecoilValue } from 'recoil'
import { ANGLE_TYPE, currentAngleTypeSelector } from '@/store/canvasAtom'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function Gable({ pitchRef, offsetRef, widthRef, radioTypeRef, pitchText }) {
const { getMessage } = useMessage()
@ -21,7 +22,19 @@ export default function Gable({ pitchRef, offsetRef, widthRef, radioTypeRef, pit
{getMessage('offset')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" defaultValue={300} ref={offsetRef} />
{/*<input type="text" className="input-origin block" defaultValue={300} ref={offsetRef} />*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
ref={offsetRef}
value={300}
options={{
allowNegative: false,
allowDecimal: false //(index !== 0),
}}
></CalculatorInput>
</div>
<span className="thin">mm</span>
</div>
@ -37,7 +50,9 @@ export default function Gable({ pitchRef, offsetRef, widthRef, radioTypeRef, pit
</div>
<div className="eaves-keraba-td">
<div className={`eaves-keraba-ico ${type === '1' ? 'act' : ''}`}>
<Image src="/static/images/canvas/eaves_icon04.svg" alt="react" width={30} height={30} />
<div style={{ width: 30, height: 30, position: 'relative' }}>
<Image src="/static/images/canvas/eaves_icon04.svg" alt="react" fill style={{ objectFit: 'contain' }} />
</div>
</div>
</div>
</div>
@ -50,7 +65,9 @@ export default function Gable({ pitchRef, offsetRef, widthRef, radioTypeRef, pit
</div>
<div className="eaves-keraba-td">
<div className={`eaves-keraba-ico ${type === '2' ? 'act' : ''}`}>
<Image src="/static/images/canvas/eaves_icon09.svg" alt="react" width={30} height={30} />
<div style={{ width: 30, height: 30, position: 'relative' }}>
<Image src="/static/images/canvas/eaves_icon09.svg" alt="react" fill style={{ objectFit: 'contain' }} />
</div>
</div>
</div>
</div>
@ -61,20 +78,38 @@ export default function Gable({ pitchRef, offsetRef, widthRef, radioTypeRef, pit
{getMessage('slope')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input
type="text"
{/*<input*/}
{/* type="text"*/}
{/* className="input-origin block"*/}
{/* defaultValue={currentAngleType === ANGLE_TYPE.SLOPE ? 4.5 : 20}*/}
{/* ref={pitchRef}*/}
{/* readOnly={type === '1'}*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
defaultValue={currentAngleType === ANGLE_TYPE.SLOPE ? 4.5 : 20}
ref={pitchRef}
value={currentAngleType === ANGLE_TYPE.SLOPE ? 4.5 : 20}
readOnly={type === '1'}
/>
onChange={(value) => {
if (pitchRef?.current) pitchRef.current.value = value
}}
options={{
allowNegative: false,
allowDecimal: true //(index !== 0),
}}
></CalculatorInput>
</div>
<span className="thin">{pitchText}</span>
</div>
</div>
<div className="eaves-keraba-td">
<div className="eaves-keraba-ico ">
<Image src="/static/images/canvas/eaves_icon05.svg" alt="react" width={30} height={30} />
<div style={{ width: 30, height: 30, position: 'relative' }}>
<Image src="/static/images/canvas/eaves_icon05.svg" alt="react" fill style={{ objectFit: 'contain' }} />
</div>
</div>
</div>
</div>
@ -85,14 +120,29 @@ export default function Gable({ pitchRef, offsetRef, widthRef, radioTypeRef, pit
{getMessage('offset')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" defaultValue={800} ref={widthRef} readOnly={type === '1'} />
{/*<input type="text" className="input-origin block" defaultValue={800} ref={widthRef} readOnly={type === '1'} />*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
ref={widthRef}
value={800}
readOnly={type === '1'}
options={{
allowNegative: false,
allowDecimal: false //(index !== 0),
}}
></CalculatorInput>
</div>
<span className="thin">mm</span>
</div>
</div>
<div className="eaves-keraba-td">
<div className="eaves-keraba-ico ">
<Image src="/static/images/canvas/eaves_icon10.svg" alt="react" width={30} height={30} />
<div style={{ width: 30, height: 30, position: 'relative' }}>
<Image src="/static/images/canvas/eaves_icon10.svg" alt="react" fill style={{ objectFit: 'contain' }} />
</div>
</div>
</div>
</div>

View File

@ -1,4 +1,5 @@
import { useMessage } from '@/hooks/useMessage'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function Shed({ offsetRef }) {
const { getMessage } = useMessage()
@ -10,7 +11,19 @@ export default function Shed({ offsetRef }) {
{getMessage('offset')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" ref={offsetRef} defaultValue={300} />
{/*<input type="text" className="input-origin block" ref={offsetRef} defaultValue={300} />*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
ref={offsetRef}
value={300}
options={{
allowNegative: false,
allowDecimal: false //(index !== 0),
}}
></CalculatorInput>
</div>
<span className="thin">mm</span>
</div>

View File

@ -1,6 +1,7 @@
import { useMessage } from '@/hooks/useMessage'
import Image from 'next/image'
import { useState } from 'react'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function WallMerge({ offsetRef, radioTypeRef }) {
const { getMessage } = useMessage()
@ -18,12 +19,14 @@ export default function WallMerge({ offsetRef, radioTypeRef }) {
<div className="eaves-keraba-th">
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra01" value="1" checked={type === '1'} onChange={(e) => onChange(e)} />
<label htmlFor="ra01">{getMessage('has.sleeve')}</label>
<label htmlFor="ra01">{getMessage('has.not.sleeve')}</label>
</div>
</div>
<div className="eaves-keraba-td">
<div className={`eaves-keraba-ico ${type === '1' ? 'act' : ''}`}>
<Image src="/static/images/canvas/eaves_icon06.svg" alt="react" width={30} height={30} />
<div style={{ width: 30, height: 30, position: 'relative' }}>
<Image src="/static/images/canvas/eaves_icon06.svg" alt="react" fill style={{ objectFit: 'contain' }} />
</div>
</div>
</div>
</div>
@ -31,12 +34,14 @@ export default function WallMerge({ offsetRef, radioTypeRef }) {
<div className="eaves-keraba-th">
<div className="d-check-radio pop">
<input type="radio" name="radio01" id="ra02" value="2" checked={type === '2'} onChange={(e) => onChange(e)} />
<label htmlFor="ra02">{getMessage('has.not.sleeve')}</label>
<label htmlFor="ra02">{getMessage('has.sleeve')}</label>
</div>
</div>
<div className="eaves-keraba-td">
<div className={`eaves-keraba-ico ${type === '2' ? 'act' : ''}`}>
<Image src="/static/images/canvas/eaves_icon07.svg" alt="react" width={30} height={30} />
<div style={{ width: 30, height: 30, position: 'relative' }}>
<Image src="/static/images/canvas/eaves_icon07.svg" alt="react" fill style={{ objectFit: 'contain' }} />
</div>
</div>
</div>
</div>
@ -47,14 +52,29 @@ export default function WallMerge({ offsetRef, radioTypeRef }) {
{getMessage('offset')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" defaultValue={300} ref={offsetRef} readOnly={type === '1'} />
{/*<input type="text" className="input-origin block" defaultValue={300} ref={offsetRef} readOnly={type === '1'} />*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
ref={offsetRef}
value={300}
readOnly={type === '1'}
options={{
allowNegative: false,
allowDecimal: false //(index !== 0),
}}
></CalculatorInput>
</div>
<span className="thin">mm</span>
</div>
</div>
<div className="eaves-keraba-td">
<div className="eaves-keraba-ico ">
<Image src="/static/images/canvas/eaves_icon08.svg" alt="react" width={30} height={30} />
<div style={{ width: 30, height: 30, position: 'relative' }}>
<Image src="/static/images/canvas/eaves_icon08.svg" alt="react" fill style={{ objectFit: 'contain' }} />
</div>
</div>
</div>
</div>

View File

@ -7,6 +7,8 @@ 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'
import { useRoofFn } from '@/hooks/common/useRoofFn'
const FLOW_DIRECTION_TYPE = {
EIGHT_AZIMUTH: 'eightAzimuth',
@ -18,6 +20,9 @@ export default function FlowDirectionSetting(props) {
const { id, pos = contextPopupPosition, target } = props
const canvas = useRecoilValue(canvasState)
const { getMessage } = useMessage()
const { setSurfaceShapePattern } = useRoofFn()
const { changeSurfaceLineType } = useSurfaceShapeBatch({})
useEffect(() => {
return () => {
@ -29,15 +34,40 @@ export default function FlowDirectionSetting(props) {
const [flowDirection, setFlowDirection] = useState(target.direction)
const { closePopup } = usePopup()
useEffect(() => {
let newCompassDeg = 0
if ([-15, 0, 15].includes(compasDeg)) {
newCompassDeg = 0
} else if ([30, 45, 60].includes(compasDeg)) {
newCompassDeg = 45
} else if ([75, 90, 105].includes(compasDeg)) {
newCompassDeg = 90
} else if ([120, 135, 150].includes(compasDeg)) {
newCompassDeg = 135
} else if ([165, 180, -165].includes(compasDeg)) {
newCompassDeg = 180
} else if ([-120, -135, -150].includes(compasDeg)) {
newCompassDeg = -135
} else if ([-105, -90, -75].includes(compasDeg)) {
newCompassDeg = -90
} else if ([-60, -45, -30].includes(compasDeg)) {
newCompassDeg = -45
} else {
newCompassDeg = ''
}
const newOrientation = orientations.find((item) => item.value === newCompassDeg)
setSelectedOrientation(newOrientation)
}, [compasDeg])
const orientations = [
{ name: `${getMessage('commons.none')}`, value: 0 },
{ name: `${getMessage('commons.south')}`, value: 360 },
{ name: `${getMessage('commons.south')}${getMessage('commons.east')}`, value: 315 },
{ name: `${getMessage('commons.south')}${getMessage('commons.west')}`, value: 45 },
{ name: `${getMessage('commons.east')}`, value: 270 },
{ name: `${getMessage('commons.west')}`, value: 90 },
{ name: `${getMessage('commons.north')}${getMessage('commons.east')}`, value: 225 },
{ name: `${getMessage('commons.north')}${getMessage('commons.west')}`, value: 135 },
{ name: `${getMessage('commons.none')}`, value: '' },
{ name: `${getMessage('commons.south')}`, value: 0 },
{ name: `${getMessage('commons.south')}${getMessage('commons.east')}`, value: 45 },
{ name: `${getMessage('commons.south')}${getMessage('commons.west')}`, value: -45 },
{ name: `${getMessage('commons.east')}`, value: 90 },
{ name: `${getMessage('commons.west')}`, value: -90 },
{ name: `${getMessage('commons.north')}${getMessage('commons.east')}`, value: 135 },
{ name: `${getMessage('commons.north')}${getMessage('commons.west')}`, value: -135 },
{ name: `${getMessage('commons.north')}`, value: 180 },
]
@ -51,8 +81,10 @@ export default function FlowDirectionSetting(props) {
surfaceCompass: orientation,
surfaceCompassType: type,
})
setSurfaceShapePattern(roof, null, null, roof.roofMaterial)
drawDirectionArrow(roof)
canvas?.renderAll()
changeSurfaceLineType(roof)
closePopup(id)
}
@ -105,6 +137,11 @@ export default function FlowDirectionSetting(props) {
value={selectedOrientation}
options={orientations}
onChange={(e) => {
if (e.value === '') {
setCompasDeg(null)
setSelectedOrientation(e)
return
}
setType(FLOW_DIRECTION_TYPE.EIGHT_AZIMUTH)
setSelectedOrientation(e)
setCompasDeg(e.value)
@ -133,31 +170,32 @@ export default function FlowDirectionSetting(props) {
<div className="draw-flow-wrap">
<div className="compas-box">
<div className="compas-box-inner">
{Array.from({ length: 180 / 15 + 1 }).map((dot, index) => (
{Array.from({ length: 180 / 15 }).map((dot, index) => (
<div
key={index}
className={`circle ${compasDeg === 15 * (12 + index) && type === FLOW_DIRECTION_TYPE.TWENTY_FOUR_AZIMUTH ? 'act' : ''}`}
className={`circle ${compasDeg === -15 * index + 180 ? 'act' : ''}`}
onClick={() => {
if (index === 0) {
setCompasDeg(180)
return
}
setType(FLOW_DIRECTION_TYPE.TWENTY_FOUR_AZIMUTH)
setCompasDeg(15 * (12 + index))
setCompasDeg(-15 * index + 180)
}}
></div>
))}
{Array.from({ length: 180 / 15 - 1 }).map((dot, index) => (
{Array.from({ length: 180 / 15 }).map((dot, index) => (
<div
key={index}
className={`circle ${compasDeg === 15 * (index + 1) && type === FLOW_DIRECTION_TYPE.TWENTY_FOUR_AZIMUTH ? 'act' : ''}`}
className={`circle ${compasDeg === -1 * 15 * index ? 'act' : ''}`}
onClick={() => {
setType(FLOW_DIRECTION_TYPE.TWENTY_FOUR_AZIMUTH)
setCompasDeg(15 * (index + 1))
setCompasDeg(15 * index * -1)
}}
></div>
))}
<div className="compas">
<div
className="compas-arr"
style={{ transform: `${type === FLOW_DIRECTION_TYPE.TWENTY_FOUR_AZIMUTH && `rotate(${compasDeg}deg)`}` }}
></div>
<div className="compas-arr" style={{ transform: `${`rotate(${-1 * compasDeg}deg)`}` }}></div>
</div>
</div>
</div>

View File

@ -4,10 +4,10 @@ import { useEffect, useState } from 'react'
import { useMessage } from '@/hooks/useMessage'
import { canvasState } from '@/store/canvasAtom'
import { useRecoilValue } from 'recoil'
import { onlyNumberInputChange } from '@/util/input-utils'
import { usePopup } from '@/hooks/usePopup'
import { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
import { useSwal } from '@/hooks/useSwal'
import { normalizeDigits } from '@/util/input-utils'
const TYPE = {
DOT: 'DOT',
@ -218,7 +218,7 @@ export default function DotLineGrid(props) {
className="input-origin"
name={`horizontalInterval`}
value={copyCurrentSetting.INTERVAL.horizontalInterval}
onChange={(e) => onlyNumberInputChange(e, changeInput)}
onChange={(e) => changeInput(normalizeDigits(e.target.value), e)}
/>
</div>
<span>mm</span>
@ -231,7 +231,7 @@ export default function DotLineGrid(props) {
className="input-origin"
name={`verticalInterval`}
value={copyCurrentSetting.INTERVAL.verticalInterval}
onChange={(e) => onlyNumberInputChange(e, changeInput)}
onChange={(e) => changeInput(normalizeDigits(e.target.value), e)}
/>
</div>
<span>mm</span>
@ -258,7 +258,7 @@ export default function DotLineGrid(props) {
className="input-origin"
name={`ratioInterval`}
value={copyCurrentSetting.INTERVAL.ratioInterval}
onChange={(e) => onlyNumberInputChange(e, changeInput)}
onChange={(e) => changeInput(normalizeDigits(e.target.value), e)}
/>
</div>
<span>mm</span>

View File

@ -4,9 +4,11 @@ import { usePopup } from '@/hooks/usePopup'
import { useRecoilValue } from 'recoil'
import { contextPopupPositionState } from '@/store/popupAtom'
import { useState } from 'react'
import { currentObjectState } from '@/store/canvasAtom'
import { useGrid } from '@/hooks/common/useGrid'
import { canvasState, currentObjectState } from '@/store/canvasAtom'
import { gridColorState } from '@/store/gridAtom'
import { gridDisplaySelector } from '@/store/settingAtom'
const GRID_PADDING = 5
export default function GridCopy(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
const { id, pos = contextPopupPosition } = props
@ -15,9 +17,39 @@ export default function GridCopy(props) {
const [length, setLength] = useState('0')
const [arrow, setArrow] = useState(null)
const currentObject = useRecoilValue(currentObjectState)
const { copy } = useGrid()
const canvas = useRecoilValue(canvasState)
const gridColor = useRecoilValue(gridColorState)
const isGridDisplay = useRecoilValue(gridDisplaySelector)
const handleApply = () => {
copy(currentObject, ['↑', '←'].includes(arrow) ? +length * -1 : +length)
copy(currentObject, ['↑', '←'].includes(arrow) ? (+length * -1) / 10 : +length / 10)
}
const copy = (object, length) => {
const lineStartX = object.direction === 'vertical' ? object.x1 + length : object.x1
const lineEndX = object.direction === 'vertical' ? object.x2 + length : object.x2
const lineStartY = object.direction === 'vertical' ? object.y1 : object.y1 + length
const lineEndY = object.direction === 'vertical' ? object.y2 : object.y1 + length
const line = new fabric.Line([lineStartX, lineStartY, lineEndX, lineEndY], {
stroke: gridColor,
strokeWidth: 1,
selectable: true,
lockMovementX: true,
lockMovementY: true,
lockRotation: true,
lockScalingX: true,
lockScalingY: true,
strokeDashArray: [5, 2],
opacity: 0.3,
padding: GRID_PADDING,
direction: object.direction,
visible: isGridDisplay,
name: object.name,
})
canvas.add(line)
canvas.setActiveObject(line)
canvas.renderAll()
}
return (
<WithDraggable isShow={true} pos={pos} className="xm">

View File

@ -3,12 +3,10 @@ import { useMessage } from '@/hooks/useMessage'
import { usePopup } from '@/hooks/usePopup'
import { useRecoilState, useRecoilValue } from 'recoil'
import { contextPopupPositionState } from '@/store/popupAtom'
import { useCanvas } from '@/hooks/useCanvas'
import { canvasState, currentObjectState } from '@/store/canvasAtom'
import { useEffect, useState } from 'react'
import { useGrid } from '@/hooks/common/useGrid'
import { useSwal } from '@/hooks/useSwal'
import { set } from 'react-hook-form'
import { normalizeDigits } from '@/util/input-utils'
export default function GridMove(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
@ -17,7 +15,6 @@ export default function GridMove(props) {
const { getMessage } = useMessage()
const { closePopup } = usePopup()
const { swalFire } = useSwal()
const { move } = useGrid()
const [currentObject, setCurrentObject] = useRecoilState(currentObjectState)
const [isAll, setIsAll] = useState(false)
const [verticalSize, setVerticalSize] = useState('0')
@ -54,21 +51,31 @@ export default function GridMove(props) {
.forEach((grid) => {
move(
grid,
arrow2 === '←' ? Number(horizonSize) * -1 : Number(horizonSize),
arrow1 === '↑' ? Number(verticalSize) * -1 : Number(verticalSize),
arrow2 === '←' ? (Number(normalizeDigits(horizonSize)) * -1) / 10 : Number(normalizeDigits(horizonSize)) / 10,
arrow1 === '↑' ? (Number(normalizeDigits(verticalSize)) * -1) / 10 : Number(normalizeDigits(verticalSize)) / 10,
)
})
} else {
move(
currentObject,
arrow2 === '←' ? Number(horizonSize) * -1 : Number(horizonSize),
arrow1 === '↑' ? Number(verticalSize) * -1 : Number(verticalSize),
arrow2 === '←' ? (Number(normalizeDigits(horizonSize)) * -1) / 10 : Number(normalizeDigits(horizonSize)) / 10,
arrow1 === '↑' ? (Number(normalizeDigits(verticalSize)) * -1) / 10 : Number(normalizeDigits(verticalSize)) / 10,
)
}
canvas.renderAll()
handleClose()
}
const move = (object, x, y) => {
object.set({
...object,
x1: object.direction === 'vertical' ? object.x1 + x : object.x1,
x2: object.direction === 'vertical' ? object.x1 + x : object.x2,
y1: object.direction === 'vertical' ? object.y1 : object.y1 + y,
y2: object.direction === 'vertical' ? object.y2 : object.y1 + y,
})
}
const handleClose = () => {
closePopup(id)
}
@ -91,7 +98,7 @@ export default function GridMove(props) {
type="text"
className="input-origin"
value={verticalSize}
onChange={(e) => setVerticalSize(e.target.value)}
onChange={(e) => setVerticalSize(normalizeDigits(e.target.value))}
readOnly={!isAll && currentObject?.direction === 'vertical'}
/>
</div>
@ -119,7 +126,7 @@ export default function GridMove(props) {
type="text"
className="input-origin"
value={horizonSize}
onChange={(e) => setHorizonSize(e.target.value)}
onChange={(e) => setHorizonSize(normalizeDigits(e.target.value))}
readOnly={!isAll && currentObject?.direction === 'horizontal'}
/>
</div>

View File

@ -1,6 +1,8 @@
import Image from 'next/image'
import { useMessage } from '@/hooks/useMessage'
import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/input-utils'
import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils'
import { CalculatorInput } from '@/components/common/input/CalcInput'
import { useEffect } from 'react'
export default function Angle({ props }) {
const { getMessage } = useMessage()
@ -14,14 +16,39 @@ export default function Angle({ props }) {
<div className="outline-form">
<span className="mr10">{getMessage('modal.cover.outline.angle')}</span>
<div className="input-grid" style={{ width: '63px' }}>
<input
type="text"
{/*<input*/}
{/* type="text"*/}
{/* className="input-origin block"*/}
{/* value={angle1}*/}
{/* ref={angle1Ref}*/}
{/* onFocus={(e) => (angle1Ref.current.value = '')}*/}
{/* onChange={(e) => setAngle1(normalizeDecimalLimit(e.target.value, 2))}*/}
{/* placeholder="45"*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={angle1}
ref={angle1Ref}
onFocus={(e) => (angle1Ref.current.value = '')}
onChange={(e) => onlyNumberWithDotInputChange(e, setAngle1)}
onChange={(value) => {
// Calculate the final value first
let finalValue = value;
const numValue = parseInt(value, 10);
if (!isNaN(numValue)) {
const clampedValue = Math.min(180, Math.max(-180, numValue));
finalValue = String(clampedValue);
}
// Set state once with the final value
setAngle1(finalValue);
}}
placeholder="45"
onFocus={() => (angle1Ref.current.value = '')}
options={{
allowNegative: true,
allowDecimal: true
}}
/>
</div>
<button
@ -34,14 +61,29 @@ export default function Angle({ props }) {
<div className="outline-form">
<span className="mr10">{getMessage('modal.cover.outline.length')}</span>
<div className="input-grid" style={{ width: '63px' }}>
<input
type="text"
{/*<input*/}
{/* type="text"*/}
{/* className="input-origin block"*/}
{/* value={length1}*/}
{/* ref={length1Ref}*/}
{/* onFocus={(e) => (length1Ref.current.value = '')}*/}
{/* onChange={(e) => setLength1(normalizeDigits(e.target.value))}*/}
{/* placeholder="3000"*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={length1}
ref={length1Ref}
onFocus={(e) => (length1Ref.current.value = '')}
onChange={(e) => onlyNumberInputChange(e, setLength1)}
onChange={(value) => setLength1(value)}
placeholder="3000"
onFocus={() => (length1Ref.current.value = '')}
options={{
allowNegative: false,
allowDecimal: false
}}
/>
</div>
<button

View File

@ -1,5 +1,6 @@
import { useMessage } from '@/hooks/useMessage'
import { onlyNumberInputChange } from '@/util/input-utils'
import { normalizeDigits } from '@/util/input-utils'
import { CalculatorInput } from '@/components/common/input/CalcInput'
export default function Diagonal({ props }) {
const { getMessage } = useMessage()
@ -30,14 +31,29 @@ export default function Diagonal({ props }) {
{getMessage('modal.cover.outline.length')}
</span>
<div className="input-grid" style={{ width: '63px' }}>
<input
type="text"
{/*<input*/}
{/* type="text"*/}
{/* className="input-origin block"*/}
{/* value={outerLineDiagonalLength}*/}
{/* ref={outerLineDiagonalLengthRef}*/}
{/* onFocus={(e) => (outerLineDiagonalLengthRef.current.value = '')}*/}
{/* onChange={(e) => setOuterLineDiagonalLength(normalizeDigits(e.target.value))}*/}
{/* placeholder="3000"*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={outerLineDiagonalLength}
ref={outerLineDiagonalLengthRef}
onFocus={(e) => (outerLineDiagonalLengthRef.current.value = '')}
onChange={(e) => onlyNumberInputChange(e, setOuterLineDiagonalLength)}
onChange={(value) => setOuterLineDiagonalLength(value)}
placeholder="3000"
onFocus={() => (outerLineDiagonalLengthRef.current.value = '')}
options={{
allowNegative: false,
allowDecimal: false
}}
/>
</div>
<button
@ -52,14 +68,29 @@ export default function Diagonal({ props }) {
<div className="outline-form">
<span className="mr10"> {getMessage('modal.cover.outline.length')}</span>
<div className="input-grid" style={{ width: '63px' }}>
<input
type="text"
{/*<input*/}
{/* type="text"*/}
{/* className="input-origin block"*/}
{/* value={length1}*/}
{/* ref={length1Ref}*/}
{/* onFocus={(e) => (length1Ref.current.value = '')}*/}
{/* onChange={(e) => setLength1(normalizeDigits(e.target.value))}*/}
{/* placeholder="3000"*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={length1}
ref={length1Ref}
onFocus={(e) => (length1Ref.current.value = '')}
onChange={(e) => onlyNumberInputChange(e, setLength1)}
onChange={(value) => setLength1(value)}
placeholder="3000"
onFocus={() => (length1Ref.current.value = '')}
options={{
allowNegative: false,
allowDecimal: false
}}
/>
</div>
<button
@ -110,14 +141,29 @@ export default function Diagonal({ props }) {
<div className="outline-form">
<span className="mr10"> {getMessage('modal.cover.outline.length')}</span>
<div className="input-grid" style={{ width: '98px' }}>
<input
type="text"
{/*<input*/}
{/* type="text"*/}
{/* className="input-origin block"*/}
{/* value={length2}*/}
{/* ref={length2Ref}*/}
{/* onChange={(e) => setLength2(normalizeDigits(e.target.value))}*/}
{/* readOnly={true}*/}
{/* placeholder="3000"*/}
{/*/>*/}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
value={length2}
ref={length2Ref}
onChange={(e) => onlyNumberInputChange(e, setLength2)}
readOnly={true}
onChange={(value) => setLength2(value)}
placeholder="3000"
onFocus={() => (length2Ref.current.value = '')}
options={{
allowNegative: false,
allowDecimal: false
}}
/>
</div>
</div>

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