Compare commits

...

399 Commits

Author SHA1 Message Date
88632a4619 sk제거 2025-11-07 08:45:51 +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
fd962219dd Merge pull request 'dev' (#126) from dev into prd-deploy
Reviewed-on: #126
2025-06-18 10:43:21 +09:00
d867a8dda7 Merge pull request 'dev' (#124) from dev into prd-deploy
Reviewed-on: #124
2025-06-16 17:13:48 +09:00
32a9ade07c Merge pull request 'dev' (#121) from dev into prd-deploy
Reviewed-on: #121
2025-06-13 15:32:54 +09:00
d4f633fb91 Merge pull request 'dev' (#119) from dev into prd-deploy
Reviewed-on: #119
2025-06-12 13:26:39 +09:00
158f8226b0 Merge pull request 'merge 하면서 빠진 부분 수정' (#116) from dev into prd-deploy
Reviewed-on: #116
2025-06-12 10:11:23 +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
8f782e7b17 Merge pull request '설치면에 module이 없는 경우 처리' (#112) from dev into prd-deploy
Reviewed-on: #112
2025-06-11 14:55:25 +09:00
0005643c94 Merge pull request '사진 찍기 전후 처리 추가' (#110) from dev into prd-deploy
Reviewed-on: #110
2025-06-11 09:31:19 +09:00
c25af0e186 Merge pull request 'dev' (#108) from dev into prd-deploy
Reviewed-on: #108
2025-06-10 14:59:12 +09:00
e19395f612 Merge pull request 'dev' (#106) from dev into prd-deploy
Reviewed-on: #106
2025-06-09 17:10:54 +09:00
9f2fe5f3c8 Merge pull request 'dev' (#103) from dev into prd-deploy
Reviewed-on: #103
2025-06-05 17:05:36 +09:00
faf4c62328 Merge pull request 'dev' (#101) from dev into prd-deploy
Reviewed-on: #101
2025-06-05 11:32:18 +09:00
c6e864d4c4 Merge pull request 'dev' (#99) from dev into dev-deploy
Reviewed-on: #99
2025-06-04 19:19:56 +09:00
190d5cc19c Merge pull request '복사 로직 오류 수정' (#97) from dev into dev-deploy
Reviewed-on: #97
2025-06-04 09:04:38 +09:00
713b773dc0 Merge pull request '견적서 복사 로직 수정' (#96) from dev into dev-deploy
Reviewed-on: #96
2025-06-04 08:56:37 +09:00
72bbf33317 Merge pull request 'dev' (#95) from dev into dev-deploy
Reviewed-on: #95
2025-06-04 08:37:35 +09:00
ac00777cf1 Merge pull request 'dev' (#94) from dev into dev-deploy
Reviewed-on: #94
2025-06-02 17:46:01 +09:00
f61e50284c Merge pull request 'dev' (#92) from dev into dev-deploy
Reviewed-on: #92
2025-06-02 14:01:05 +09:00
cf462dc4a3 Merge pull request 'dev' (#91) from dev into dev-deploy
Reviewed-on: #91
2025-06-02 11:10:51 +09:00
aec7bb8c03 Merge pull request 'dev' (#89) from dev into dev-deploy
Reviewed-on: #89
2025-06-02 10:52:11 +09:00
bc607d4828 Merge pull request 'dev' (#88) from dev into dev-deploy
Reviewed-on: #88
2025-06-02 09:06:41 +09:00
a58604478d Merge pull request 'dev' (#87) from dev into dev-deploy
Reviewed-on: #87
2025-05-30 17:30:40 +09:00
b71fb0992f Merge pull request 'dev' (#86) from dev into dev-deploy
Reviewed-on: #86
2025-05-30 15:48:09 +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
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
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
72cded314b Merge pull request 'dev' (#85) from dev into dev-deploy
Reviewed-on: #85
2025-05-29 16:45:27 +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
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
15b1cc84fd Merge pull request 'dev' (#68) from dev into dev-deploy
Reviewed-on: #68
2025-05-28 11:41:35 +09:00
d7b17e491f Merge pull request 'dev' (#66) from dev into dev-deploy
Reviewed-on: #66
2025-05-28 10:43:41 +09:00
a83f0d7017 Merge pull request 'dev' (#65) from dev into dev-deploy
Reviewed-on: #65
2025-05-28 09:09:53 +09:00
c42c36501a Merge pull request 'dev' (#63) from dev into dev-deploy
Reviewed-on: #63
2025-05-27 16:58:29 +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
a5a1370135 Merge pull request 'dev' (#58) from dev into dev-deploy
Reviewed-on: #58
2025-05-26 16:56:30 +09:00
41ab66eb10 Merge pull request 'dev' (#56) from dev into dev-deploy
Reviewed-on: #56
2025-05-26 10:26:59 +09:00
745665782c - 동선 이동 시 라인 안잡히는 현상 수정 2025-05-23 16:33:42 +09:00
김민식
765855b361 외벽선 작성 팝업 안내 문구 추가 및 관련 다국어 추가 2025-05-23 16:33:42 +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
d0eb50313a Merge pull request 'dev' (#54) from dev into dev-deploy
Reviewed-on: #54
2025-05-23 14:51:20 +09:00
03ec99dbb4 Merge pull request 'dev' (#52) from dev into dev-deploy
Reviewed-on: #52
2025-05-23 14:33:52 +09:00
68d961858f Merge pull request 'dev' (#50) from dev into dev-deploy
Reviewed-on: #50
2025-05-23 10:38:05 +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
c52d372148 Merge pull request '지붕덮개 버그 수정' (#48) from dev into dev-deploy
Reviewed-on: #48
2025-05-21 17:33:32 +09:00
169 changed files with 12278 additions and 1672 deletions

View File

@ -10,7 +10,8 @@ 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_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"
@ -29,4 +30,7 @@ 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"
S3_PROFILE="dev"
#logging
NEXT_PUBLIC_ENABLE_LOGGING=true

View File

@ -10,7 +10,8 @@ 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_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"
@ -29,4 +30,7 @@ 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"
S3_PROFILE="dev"
#logging
NEXT_PUBLIC_ENABLE_LOGGING=false

View File

@ -10,12 +10,11 @@ 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_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"
@ -30,4 +29,7 @@ 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"
S3_PROFILE="prd"
#logging
NEXT_PUBLIC_ENABLE_LOGGING=false

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:

View File

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

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

Binary file not shown.

View File

@ -38,10 +38,14 @@ const cropImage = async (Key, width, height, left, top) => {
}
const buffer = Buffer.concat(chunks)
const image = await Jimp.read(buffer)
let image = await Jimp.read(buffer)
image.autocrop({ tolerance: 0.0002, leaveBorder: 10 })
return await image.getBuffer('image/png')
const resizedImage = await resizeImage(image).then((result) => {
return result
})
return await resizedImage.getBuffer('image/png')
// Convert stream to buffer
// const chunks = []
@ -77,6 +81,56 @@ const cropImage = async (Key, width, height, left, top) => {
}
}
//크롭된 이미지를 배경 크기에 맞게 리사이즈
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()

View File

@ -21,6 +21,8 @@ const defaultEstimateData = {
fileList: [],
fileFlg: '0', //후일 자료 제출 (체크 1 노체크 0)
priceCd: '',
pricingFlag: false, // 가격 처리 플래그 추가
}
/**

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: {
@ -212,6 +213,13 @@ export const SAVE_KEY = [
'endPoint',
'editable',
'isSortedPoints',
'isMultipleOf45',
'from',
'originColor',
'originWidth',
'originHeight',
'skeletonLines',
'skeleton'
]
export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype, fabric.Group.prototype]

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}

View File

@ -0,0 +1,442 @@
import React, { useState, useRef, useEffect, forwardRef } 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 }, 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 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 = ''
if (hasOperation) {
//
if (calculator.currentOperand === '0' || calculator.shouldResetDisplay) {
calculator.currentOperand = num.toString()
calculator.shouldResetDisplay = false
} else {
calculator.currentOperand = (calculator.currentOperand || '') + num
}
newDisplayValue = calculator.previousOperand + calculator.operation + calculator.currentOperand
setDisplayValue(newDisplayValue)
} else {
//
if (displayValue === '0' || calculator.shouldResetDisplay) {
calculator.currentOperand = num.toString()
calculator.shouldResetDisplay = false
newDisplayValue = calculator.currentOperand
setDisplayValue(newDisplayValue)
if (!hasOperation) {
onChange(calculator.currentOperand)
}
} 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(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') {
setShowKeypad(false)
return
}
//
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === 'ArrowUp' || e.key === 'ArrowDown') {
setShowKeypad(true)
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
}
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}
/>
{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({
@ -27,6 +28,7 @@ export default function QSelectBox({
showKey = '',
params = {},
tagTitle = '',
showFirstOptionWhenEmpty = false,
}) {
const { getMessage } = useMessage()
@ -39,7 +41,9 @@ export default function QSelectBox({
if (options.length === 0) return title !== '' ? title : getMessage('selectbox.title')
if (showKey !== '' && !value) {
//value showKey
// return options[0][showKey]
if (showFirstOptionWhenEmpty && options.length > 0) {
return options[0][showKey]
}
return title !== '' ? title : getMessage('selectbox.title')
} else if (showKey !== '' && value) {
//value sourceKey targetKey
@ -48,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')
}
}
@ -74,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)

View File

@ -25,6 +25,7 @@ export default function QnaDetailModal({ qnaNo, setOpen, qnaType }) {
compCd : 5200,
loginId : sessionState.userId,
langCd : 'JA',
siteTpCd : 'QC',
})
const apiUrl = `${url}?${params.toString()}`

View File

@ -22,7 +22,8 @@ export default function QnaRegModal({ setOpen, setReload, searchValue, selectPag
const [sessionState, setSessionState] = useRecoilState(sessionStore)
const globalLocaleState = useRecoilValue(globalLocaleStore)
const [files, setFiles] = useState([])
const [qnaData, setQnaData] = useState([])
//const [qnaData, setQnaData] = useState([])
const [qnaData, setQnaData] = useState({})
const [closeMdFlg, setCloseMdFlg] = useState(true)
const [closeSmFlg, setCloseSmFlg] = useState(true)
const [hideSmFlg, setHideSmFlg] = useState(false)
@ -44,6 +45,10 @@ export default function QnaRegModal({ setOpen, setReload, searchValue, selectPag
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
@ -80,14 +85,16 @@ let fileCheck = false;
//setQnaData([])
setQnaData({
...qnaData,
compCd: "5200",
siteTpCd: "QC",
schNoticeClsCd: "QNA",
regId: sessionState.userId,
storeId: sessionState.userId,
qstMail : sessionState.email
})
regId: sessionState?.userId || '',
storeId: sessionState?.userId || '',
qstMail: sessionState?.email || '',
qnaClsLrgCd: '',
qnaClsMidCd: '',
qnaClsSmlCd: ''
});
const codeL = findCommonCode(204200)
if (codeL != null) {
@ -119,41 +126,42 @@ let fileCheck = false;
}
const onChangeQnaTypeM = (e) => {
if (!e?.clCode) return;
if(e === undefined || e === null) return;
const codeS = findCommonCode(204400)
if (codeS != null) {
let codeList = []
codeS.map((item) => {
if (item.clRefChr1 === e.clCode) {
codeList.push(item);
}
})
setQnaData({ ...qnaData, qnaClsMidCd: e.clCode })
setCloseSmFlg(false)
setQnaTypeSmCodeList(codeList)
qnaTypeSmCodeRef.current?.setValue();
if(codeList.length > 0) {
setHideSmFlg(false)
}else{
setHideSmFlg(true)
}
//
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 === undefined || e === null) return;
setQnaData({ ...qnaData, qnaClsSmlCd:e.clCode})
if (!e?.clCode) return;
setQnaData(prevState => ({
...prevState,
qnaClsSmlCd: e.clCode
}));
}
const onFileSave = () => {

View File

@ -23,6 +23,7 @@ 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'
export default function Estimate({}) {
const [uniqueData, setUniqueData] = useState([])
const [handlePricingFlag, setHandlePricingFlag] = useState(false)
@ -137,7 +138,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)
}
})
@ -152,6 +173,19 @@ 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?? currentPid)
@ -507,6 +541,7 @@ export default function Estimate({}) {
icon: 'warning',
confirmFn: () => {
handlePricing(showPriceCd)
setEstimateContextState({ pricingFlag:true })
},
})
}
@ -644,11 +679,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,
@ -662,7 +700,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'
@ -700,7 +738,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 {
@ -731,7 +770,7 @@ export default function Estimate({}) {
/* 케이블 select 변경시 */
const onChangeDisplayCableItem = (value, itemList) => {
itemList.map((item, index) => {
if (item.dispCableFlg === '1' && item.itemTpCd !== 'M12') {
if (item.dispCableFlg === '1' && item.itemTpCd !== 'M12' && item.itemTpCd !== 'S13') {
if (value !== '') {
onChangeDisplayItem(value, item.dispOrder, index, true)
}
@ -743,7 +782,7 @@ export default function Estimate({}) {
/* 케이블 select 변경시 */
const onChangeDisplayDoubleCableItem = (value, itemList) => {
itemList.map((item, index) => {
if (item.dispCableFlg === '1' && item.itemTpCd === 'M12') {
if (item.dispCableFlg === '1' && (item.itemTpCd === 'M12' || item.itemTpCd === 'S13')) {
if (value !== '') {
onChangeDisplayItem(value, item.dispOrder, index, true)
}
@ -939,7 +978,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
@ -974,7 +1013,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
@ -1008,8 +1047,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
@ -1068,7 +1107,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
@ -1101,9 +1140,9 @@ export default function Estimate({}) {
item.showSaleTotPrice = '0'
}
if (item.dispCableFlg === '1' ) {
if (item.dispCableFlg === '1') {
dispCableFlgCnt++
if(item.itemTpCd === 'M12') {
if(item.itemTpCd === 'M12' || item.itemTpCd === 'S13') {
setCableDbItem(item.itemId)
}else{
setCableItem(item.itemId)
@ -1117,9 +1156,10 @@ export default function Estimate({}) {
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
@ -1149,7 +1189,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
@ -1175,11 +1215,7 @@ export default function Estimate({}) {
if (item.dispCableFlg === '1') {
dispCableFlgCnt++
}
if (item.dispCableFlg === '1'){
if(item.itemTpCd === 'M12') {
if(item.itemTpCd === 'M12' || item.itemTpCd === 'S13') {
setCableDbItem(item.itemId)
}else{
setCableItem(item.itemId)
@ -1437,7 +1473,7 @@ export default function Estimate({}) {
onChange={(e) => {
//
setHandlePricingFlag(true)
setEstimateContextState({ estimateType: e.target.value })
setEstimateContextState({ estimateType: e.target.value, setEstimateContextState })
}}
/>
<label htmlFor="YJSS">{getMessage('estimate.detail.estimateType.yjss')}</label>
@ -1457,7 +1493,7 @@ export default function Estimate({}) {
<label htmlFor="YJOD">{getMessage('estimate.detail.estimateType.yjod')}</label>
</div>
</div>
{session?.storeLvl === '1' && agencyCustList.length > 0 ? (
{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' }}>
@ -1995,6 +2031,7 @@ export default function Estimate({}) {
classNamePrefix="custom"
placeholder="Select"
options={originDisplayItemList}
styles={groupStyles}
onChange={(e) => {
if (isObjectNotEmpty(e)) {
onChangeDisplayItem(e.itemId, item.dispOrder, index, false)
@ -2031,13 +2068,13 @@ export default function Estimate({}) {
getOptionValue={(x) => x.clRefChr1}
components={{
SingleValue: ({ children, ...props }) => {
return <components.SingleValue {...props}>{(item.itemTpCd === 'M12')? item.itemName : props.data.clRefChr3}</components.SingleValue>
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 (item.itemTpCd === 'M12')? item.itemId : option.clRefChr1 === item.itemId
return (item.itemTpCd === 'M12' || item.itemTpCd === 'S13' )? item.itemId : option.clRefChr1 === item.itemId
})}
/>
)}

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,23 +71,14 @@ 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;
},
addLengthText() {
@ -182,4 +178,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, drawRidgeRoof, drawShedRoof, toGeoJSON } from '@/util/qpolygon-utils'
import { calculateAngle, drawGableRoof, drawRidgeRoof, 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',
@ -87,6 +88,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() {
@ -126,11 +131,13 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
this.on('moving', () => {
this.initLines()
this.addLengthText()
this.setCoords()
})
this.on('modified', (e) => {
this.initLines()
this.addLengthText()
this.setCoords()
})
this.on('selected', () => {
@ -210,8 +217,26 @@ 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, idx) => {
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))
},
/**
* 보조선 그리기
@ -219,9 +244,20 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
*/
drawHelpLine(settingModalFirstOptions) {
/* innerLines 초기화 */
this.innerLines.forEach((line) => {
this.canvas.remove(line)
})
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.canvas.renderAll()
let textMode = 'plane'
@ -241,50 +277,76 @@ 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 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 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))
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 angle1 === angle2
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])
})
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
}
})
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) {
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)
const getParallelEavesLines = function (shedLines, lines) {
const eavesLines = lines.filter((line) => line.attributes?.type === LINE_TYPE.WALLLINE.EAVES)
const referenceAngle = calculateAngle(shedLines[0].startPoint, shedLines[0].endPoint)
return eavesLines.filter((line) => {
const eavesAngle = calculateAngle(line.startPoint, line.endPoint)
return Math.abs(referenceAngle - eavesAngle) === 180
})
}
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('용마루 지붕')
drawRidgeRoof(this.id, this.canvas, textMode)
//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 {
console.log('변별로 설정')
drawRidgeRoof(this.id, this.canvas, textMode)
}
},
@ -314,9 +376,27 @@ 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()
@ -369,7 +449,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
@ -671,13 +759,119 @@ 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 고려한 좌표 변환
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)
}
}
// 오브젝트의 transform matrix를 고려한 좌표 변환
const matrix = this.calcTransformMatrix()
const invertedMatrix = fabric.util.invertTransform(matrix)
const transformedPoint = fabric.util.transformPoint(localPoint, invertedMatrix)
// pathOffset을 고려한 최종 좌표 계산
const pathOffset = this.get('pathOffset')
const finalPoint = {
x: Number((transformedPoint.x + pathOffset.x).toFixed(this.toFixed)),
y: Number((transformedPoint.y + pathOffset.y).toFixed(this.toFixed)),
}
if (this.name === POLYGON_TYPE.ROOF && this.isFixed) {
const isInside = this.inPolygon(point)
this.set('selectable', isInside)
const isInside = this.inPolygonImproved(finalPoint)
if (!this.selectable) {
this.set('selectable', isInside)
}
return isInside
} else {
return this.callSuper('containsPoint', point)
return this.inPolygonImproved(finalPoint)
}
},

View File

@ -2,7 +2,7 @@
import { useContext, useEffect, useRef } from 'react'
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
import { useRecoilValue, useResetRecoilState } from 'recoil'
import QContextMenu from '@/components/common/context-menu/QContextMenu'
import PanelBatchStatistics from '@/components/floor-plan/modal/panelBatch/PanelBatchStatistics'
@ -12,8 +12,8 @@ import { usePlan } from '@/hooks/usePlan'
import { useContextMenu } from '@/hooks/useContextMenu'
import { useCanvasConfigInitialize } from '@/hooks/common/useCanvasConfigInitialize'
import { currentMenuState } from '@/store/canvasAtom'
import { roofMaterialsAtom, totalDisplaySelector } from '@/store/settingAtom'
import { MENU, POLYGON_TYPE } from '@/common/common'
import { totalDisplaySelector } from '@/store/settingAtom'
import { POLYGON_TYPE } from '@/common/common'
import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider'
import { QcastContext } from '@/app/QcastProvider'
import {
@ -30,8 +30,6 @@ import { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
import { useEvent } from '@/hooks/useEvent'
import { compasDegAtom } from '@/store/orientationAtom'
import { ROOF_MATERIAL_LAYOUT } from '@/components/floor-plan/modal/placementShape/PlacementShapeSetting'
import { useMasterController } from '@/hooks/common/useMasterController'
import { hotkeyStore } from '@/store/hotkeyAtom'
import { usePopup } from '@/hooks/usePopup'
@ -70,10 +68,29 @@ export default function CanvasFrame() {
canvas?.renderAll() // .
if (canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE).length > 0) {
setSelectedMenu('module')
setTimeout(() => {
setSelectedMenu('module')
}, 500)
} else if (canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL).length > 0) {
setSelectedMenu('outline')
} else {
setSelectedMenu('surface')
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 {
@ -124,12 +141,12 @@ export default function CanvasFrame() {
/**
* 캔버스가 있을 경우 핫키 이벤트 처리
* hotkeyStore에 핫키 이벤트 리스너 추가
*
*
* const hotkeys = [
{ key: 'c', fn: () => asdf() },
{ key: 'v', fn: () => qwer() },
]
setHotkeyStore(hotkeys)
{ key: 'c', fn: () => asdf() },
{ key: 'v', fn: () => qwer() },
]
setHotkeyStore(hotkeys)
*/
const hotkeyState = useRecoilValue(hotkeyStore)
const hotkeyHandlerRef = useRef(null)

View File

@ -118,7 +118,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,
@ -196,6 +196,14 @@ 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)
@ -238,10 +246,13 @@ export default function CanvasMenu(props) {
await reloadCanvasStatus(objectNo, currentCanvasPlan?.planNo ?? pid)
break
case 'estimate':
if (!isAllComplete()) {
swalFire({ text: getMessage('estimate.menu.move.valid1') })
return
if (selectedMenu !== 'simulation') {
if (!isAllComplete()) {
swalFire({ text: getMessage('estimate.menu.move.valid1') })
return
}
}
setIsGlobalLoading(true)
promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan?.planNo ?? pid}/detail` }).then((res) => {
if (res.status === 200) {
@ -312,7 +323,6 @@ export default function CanvasMenu(props) {
const settingsModalOptions = useRecoilState(settingModalFirstOptionsState)
useEffect(() => {
console.log(selectedMenu)
if (selectedMenu === 'placement') {
onClickPlacementInitialMenu()
}

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,7 @@ 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'
export default function AuxiliaryEdit(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
@ -40,15 +41,15 @@ export default function AuxiliaryEdit(props) {
if (currentObject) {
copy(
currentObject,
arrow2 ? (arrow2 === '←' ? Number(+horizonSize / 10) * -1 : Number(+horizonSize / 10)) : 0,
arrow1 ? (arrow1 === '↑' ? Number(+verticalSize / 10) * -1 : Number(+verticalSize / 10)) : 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 / 10) * -1 : Number(+horizonSize / 10)) : 0,
arrow1 ? (arrow1 === '↑' ? Number(+verticalSize / 10) * -1 : Number(+verticalSize / 10)) : 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 +66,7 @@ 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))} />
</div>
<span>mm</span>
<div className="direction-move-wrap">
@ -87,7 +88,7 @@ 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))} />
</div>
<span>mm</span>
<div className="direction-move-wrap">

View File

@ -7,6 +7,7 @@ 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'
export default function AuxiliarySize(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
@ -42,7 +43,8 @@ export default function AuxiliarySize(props) {
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())

View File

@ -10,6 +10,7 @@ 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'
export default function Module({ setTabNum }) {
const { getMessage } = useMessage()
@ -188,7 +189,7 @@ export default function Module({ setTabNum }) {
type="text"
className="input-origin block"
value={inputInstallHeight}
onChange={(e) => setInputInstallHeight(e.target.value)}
onChange={(e) => setInputInstallHeight(normalizeDecimal(e.target.value))}
/>
</div>
<span className="thin">m</span>
@ -225,7 +226,7 @@ export default function Module({ setTabNum }) {
type="text"
className="input-origin block"
value={inputVerticalSnowCover}
onChange={(e) => setInputVerticalSnowCover(e.target.value)}
onChange={(e) => setInputVerticalSnowCover(normalizeDecimal(e.target.value))}
/>
</div>
<span className="thin">cm</span>

View File

@ -7,15 +7,21 @@ 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'
export const Orientation = forwardRef((props, ref) => {
const { getMessage } = useMessage()
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,
@ -65,8 +71,13 @@ export const Orientation = forwardRef((props, ref) => {
],
}
const allOption = {
moduleSerCd: 'ALL',
moduleSerNm: getMessage("board.sub.total") || 'ALL'
};
useEffect(() => {
if (basicSetting.roofSizeSet == '3') {
if (basicSetting.roofSizeSet === '3') {
restoreModuleInstArea()
}
}, [])
@ -79,9 +90,22 @@ export const Orientation = forwardRef((props, ref) => {
useEffect(() => {
if (selectedModules) {
setSelectedModules(moduleList.find((module) => module.itemId === selectedModules.itemId))
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])
}, [selectedModules, moduleList, moduleSeriesList])
useEffect(() => {
if (selectedSurfaceType) {
@ -163,7 +187,7 @@ export const Orientation = forwardRef((props, ref) => {
title: getMessage('module.not.found'),
icon: 'warning',
})
return
}
}
}
@ -177,9 +201,10 @@ export const Orientation = forwardRef((props, ref) => {
setInputCompasDeg('-0')
return
}
if (Number(e) >= -180 && Number(e) <= 180) {
if (numberCheck(Number(e))) {
setInputCompasDeg(Number(e))
const n = Number(normalizeDecimal(e))
if (n >= -180 && n <= 180) {
if (numberCheck(n)) {
setInputCompasDeg(n)
}
} else {
setInputCompasDeg(compasDeg)
@ -204,6 +229,41 @@ export const Orientation = forwardRef((props, ref) => {
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)
@ -262,6 +322,71 @@ export const Orientation = forwardRef((props, ref) => {
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">
@ -275,31 +400,31 @@ export const Orientation = forwardRef((props, ref) => {
{Array.from({ length: 180 / 15 }).map((dot, index) => (
<div
key={index}
className={`circle ${getDegreeInOrientation(inputCompasDeg) === -1 * (-15 * index + 180) || (index === 0 && inputCompasDeg >= 172 && index === 0 && inputCompasDeg <= 180) || (inputCompasDeg === -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) {
setInputCompasDeg(180)
return
}
setInputCompasDeg(-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 ${inputCompasDeg !== 180 && getDegreeInOrientation(inputCompasDeg) === 15 * index ? 'act' : ''}`}
onClick={() => setInputCompasDeg(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(inputCompasDeg)}deg)` }}></div>
<div className="compas-arr" style={{ transform: `rotate(${-1 * getDegreeInOrientation(inputCompasDeg)}deg)` }}></div>
</div>
</div>
</div>
@ -328,16 +453,32 @@ export const Orientation = forwardRef((props, ref) => {
<div className="compas-table-wrap">
<div className="compas-table-box mb10">
<div className="outline-form mb10">
<span>{getMessage('modal.module.basic.setting.module.setting')}</span>
<span>{getMessage('modal.module.basic.setting.module.series.setting')}</span>
<div className="grid-select">
{moduleList && (
<div className="grid-select">
<QSelectBox
options={moduleList}
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>
@ -388,18 +529,18 @@ export const Orientation = forwardRef((props, ref) => {
</tbody>
</table>
</div>
{basicSetting && basicSetting.roofSizeSet == '3' && (
{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="number" className="input-origin block" value={inputMargin} onChange={(e) => setInputMargin(e.target.value)} />
<input type="text" className="input-origin block" value={inputMargin} onChange={(e) => setInputMargin(normalizeDecimal(e.target.value))} />
</div>
<span className="thin">m</span>
</div>
)}
</div>
{basicSetting && basicSetting.roofSizeSet != '3' && (
{basicSetting && basicSetting.roofSizeSet !== '3' && (
<div className="compas-table-box">
<div className="compas-grid-table">
<div className="outline-form">
@ -421,10 +562,10 @@ export const Orientation = forwardRef((props, ref) => {
<span>{getMessage('modal.module.basic.setting.module.fitting.height')}</span>
<div className="input-grid mr10">
<input
type="number"
type="text"
className="input-origin block"
value={inputInstallHeight}
onChange={(e) => handleChangeInstallHeight(e.target.value)}
onChange={(e) => handleChangeInstallHeight(normalizeDecimal(e.target.value))}
/>
</div>
<span className="thin">m</span>
@ -449,10 +590,10 @@ export const Orientation = forwardRef((props, ref) => {
<span>{getMessage('modal.module.basic.setting.module.standard.snowfall.amount')}</span>
<div className="input-grid mr10">
<input
type="number"
type="text"
className="input-origin block"
value={inputVerticalSnowCover}
onChange={(e) => handleChangeVerticalSnowCover(e.target.value)}
onChange={(e) => handleChangeVerticalSnowCover(normalizeDecimal(e.target.value))}
/>
</div>
<span className="thin">cm</span>

View File

@ -12,6 +12,7 @@ import {
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) => {
@ -155,7 +156,7 @@ const Placement = forwardRef((props, refs) => {
newLayoutSetup[index] = {
...newLayoutSetup[index],
moduleId: itemId,
[e.target.name]: Number(e.target.value),
[e.target.name]: Number(normalizeDigits(e.target.value)),
}
props.setLayoutSetup(newLayoutSetup)
}
@ -168,6 +169,7 @@ const Placement = forwardRef((props, refs) => {
<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' ? (
@ -180,6 +182,7 @@ const Placement = forwardRef((props, refs) => {
)}
</th>
))}
</tr>
</thead>
<tbody>
{selectedModules?.itemList &&
@ -215,7 +218,7 @@ const Placement = forwardRef((props, refs) => {
className="input-origin block"
name="row"
value={props.layoutSetup[index]?.row ?? 1}
defaultValue={0}
//defaultValue={0}
onChange={(e) => handleLayoutSetup(e, item.itemId, index)}
/>
</div>
@ -227,7 +230,7 @@ const Placement = forwardRef((props, refs) => {
className="input-origin block"
name="col"
value={props.layoutSetup[index]?.col ?? 1}
defaultValue={0}
//defaultValue={0}
onChange={(e) => handleLayoutSetup(e, item.itemId, index)}
/>
</div>

View File

@ -9,6 +9,7 @@ import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedM
import { forwardRef, useContext, useEffect, useImperativeHandle, useRef, useState } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import Swal from 'sweetalert2'
import { normalizeDigits } from '@/util/input-utils'
const Trestle = forwardRef((props, ref) => {
const { tabNum, setTabNum, trestleTrigger, roofs, setRoofs, moduleSelectionData, setModuleSelectionData, setRoofsStore } = props
@ -17,6 +18,12 @@ const Trestle = forwardRef((props, ref) => {
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,
@ -58,15 +65,28 @@ const Trestle = forwardRef((props, ref) => {
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 && !selectedRoof) {
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])
}, [roofs, selectedRoof]) // selectedRoof
useEffect(() => {
if (flag && moduleSelectionData) {
@ -100,41 +120,86 @@ const Trestle = forwardRef((props, ref) => {
useEffect(() => {
if (trestleList.length > 0) {
setSelectedTrestle(trestleList.find((trestle) => trestle.trestleMkrCd === trestleState?.trestleMkrCd) ?? null)
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])
useEffect(() => {
if (roofBaseList.length > 0) {
setSelectedRoofBase(roofBaseList.find((roofBase) => roofBase.roofBaseCd === trestleState?.roofBaseCd) ?? null)
} else {
setSelectedRoofBase(null)
}
}, [roofBaseList])
}, [trestleList, autoSelectStep])
useEffect(() => {
if (constMthdList.length > 0) {
setSelectedConstMthd(constMthdList.find((constMthd) => constMthd.constMthdCd === trestleState?.constMthdCd) ?? null)
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])
}, [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) {
setSelectedConstruction(constructionList.find((construction) => construction.constTp === trestleState?.construction?.constTp) ?? null)
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]), // .
title: getMessage('modal.module.basic.settting.module.error4', [selectedRoof?.nameJp]),
icon: 'warning',
})
}
} else {
setSelectedConstruction(null)
}
}, [constructionList])
}, [constructionList, autoSelectStep])
const getConstructionState = (index) => {
if (constructionList && constructionList.length > 0) {
@ -151,6 +216,13 @@ const Trestle = forwardRef((props, ref) => {
const onChangeLength = (e) => {
setLengthBase(e)
//
setSelectedRaftBase(null)
setSelectedTrestle(null)
setSelectedConstMthd(null)
setSelectedRoofBase(null)
setSelectedConstruction(null)
dispatch({
type: 'SET_LENGTH',
roof: {
@ -160,10 +232,24 @@ const Trestle = forwardRef((props, ref) => {
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: {
@ -172,58 +258,115 @@ const Trestle = forwardRef((props, ref) => {
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,
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,
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,
trestleMkrCd: selectedTrestle.trestleMkrCd,
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,
trestleMkrCd: selectedTrestle.trestleMkrCd,
constMthdCd: selectedConstMthd.constMthdCd,
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 ?? '',
@ -233,6 +376,11 @@ const Trestle = forwardRef((props, ref) => {
roofPitch: Math.round(hajebichi ?? 0),
},
})
// API (construction)
setTimeout(() => {
setAutoSelectStep('construction')
}, AUTO_SELECT_TIMEOUT)
}
const handleConstruction = (index) => {
@ -242,7 +390,8 @@ const Trestle = forwardRef((props, ref) => {
roof: {
moduleTpCd: selectedModules.itemTp ?? '',
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
raft: selectedRaftBase?.clCode,
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
//hajebichi: selectedRaftBase?.hajebichi ?? selectedRoof?.hajebichi,
trestleMkrCd: selectedTrestle.trestleMkrCd,
constMthdCd: selectedConstMthd.constMthdCd,
roofBaseCd: selectedRoofBase.roofBaseCd,
@ -279,7 +428,7 @@ const Trestle = forwardRef((props, ref) => {
ridgeMargin,
kerabaMargin,
roofIndex: selectedRoof.index,
raft: selectedRaftBase?.clCode,
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
trestle: {
hajebichi: hajebichi,
length: lengthBase,
@ -316,8 +465,8 @@ const Trestle = forwardRef((props, ref) => {
ridgeMargin,
kerabaMargin,
roofIndex: roof.index,
raft: selectedRaftBase?.clCode,
hajebichi: hajebichi,
raft: selectedRaftBase?.clCode ?? selectedRoof?.raft ?? '',
//hajebichi: selectedRaftBase?.hajebichi ?? selectedRoof?.hajebichi ?? 0,
trestle: {
length: lengthBase,
hajebichi: hajebichi,
@ -327,7 +476,8 @@ const Trestle = forwardRef((props, ref) => {
...selectedRoofBase,
},
construction: {
...constructionList.find((data) => data.constTp === trestleState.constTp),
//...constructionList.find((data) => newAddedRoofs[index].construction.constTp === data.constTp),
...constructionList.find((data) => trestleState.constTp === data.constTp),
cvrYn,
snowGdPossYn,
cvrChecked,
@ -401,7 +551,7 @@ const Trestle = forwardRef((props, ref) => {
}
if (['C', 'R'].includes(roof.roofPchAuth)) {
if (!roof?.roofPchBase) {
if (!roof?.hajebichi) {
Swal.fire({
title: getMessage('modal.module.basic.settting.module.error7', [roof.nameJp]), // .
icon: 'warning',
@ -551,7 +701,19 @@ const Trestle = forwardRef((props, ref) => {
type="text"
className="input-origin block"
value={lengthBase}
onChange={(e) => onChangeLength(e.target.value)}
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>
@ -574,6 +736,8 @@ const Trestle = forwardRef((props, ref) => {
showKey={'clCodeNm'}
disabled={selectedRoof.raftAuth === 'R'}
onChange={(e) => onChangeRaftBase(e)}
showFirstOptionWhenEmpty={true}
/>
)}
</div>
@ -591,7 +755,19 @@ const Trestle = forwardRef((props, ref) => {
type="text"
className="input-origin block"
disabled={selectedRoof.roofPchAuth === 'R'}
onChange={(e) => onChangeHajebichi(e.target.value)}
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>
@ -612,6 +788,7 @@ const Trestle = forwardRef((props, ref) => {
targetKey={'trestleMkrCd'}
showKey={'trestleMkrCdJp'}
onChange={(e) => onChangeTrestleMaker(e)}
showFirstOptionWhenEmpty={true}
/>
)}
</div>
@ -630,6 +807,7 @@ const Trestle = forwardRef((props, ref) => {
targetKey={'constMthdCd'}
showKey={'constMthdCdJp'}
onChange={(e) => onChangeConstMthd(e)}
showFirstOptionWhenEmpty={true}
/>
)}
</div>
@ -648,6 +826,7 @@ const Trestle = forwardRef((props, ref) => {
showKey={'roofBaseCdJp'}
value={selectedRoofBase}
onChange={(e) => onChangeRoofBase(e)}
showFirstOptionWhenEmpty={true}
/>
)}
</div>

View File

@ -1,16 +1,13 @@
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 { useMasterController } from '@/hooks/common/useMasterController'
import { correntObjectNoState } from '@/store/settingAtom'
import { useRecoilValue } from 'recoil'
import { useRecoilState, useRecoilValue } 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 { useSwal } from '@/hooks/useSwal'
import { canvasState, canvasZoomState } from '@/store/canvasAtom'
@ -21,9 +18,7 @@ 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 { fabric } from 'fabric'
import { fontSelector } from '@/store/fontAtom'
@ -107,12 +102,84 @@ export default function CircuitTrestleSetting({ id }) {
}
}, [])
const capture = async (type) => {
beforeCapture(type)
await handleCanvasToPng(type)
afterCapture(type)
return new Promise((resolve) => {
setTimeout(() => {
resolve(true)
}, 1000)
})
}
//
const beforeCapture = () => {
// setCanvasZoom(100)
const x = canvas.width / 2
const y = canvas.height / 2
canvas.zoomToPoint(new fabric.Point(x, y), 0.5)
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 })
})
}
canvas.renderAll()
// roof polygon
const roofPolygons = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
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')
@ -120,13 +187,27 @@ export default function CircuitTrestleSetting({ id }) {
}
//
const afterCapture = () => {
const afterCapture = (type) => {
setCanvasZoom(100)
canvas.set({ zoom: 1 })
canvas.viewportTransform = [1, 0, 0, 1, 0, 0]
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()
}
@ -387,9 +468,7 @@ export default function CircuitTrestleSetting({ id }) {
.map((obj) => {
obj.pcses = getStepUpListData()
})
beforeCapture()
handleCanvasToPng(1)
afterCapture()
await capture(1)
// result=null
@ -406,9 +485,7 @@ export default function CircuitTrestleSetting({ id }) {
const result = await getEstimateData()
if (result) {
beforeCapture()
handleCanvasToPng(2)
afterCapture()
await capture(2)
//
await saveEstimate(result)
} else {

View File

@ -74,11 +74,9 @@ export default function PowerConditionalSelect(props) {
]
useEffect(() => {
if (makers.length === 0) {
getPcsMakerList().then((res) => {
setMakers(res.data)
})
}
getPcsMakerList().then((res) => {
setMakers(res.data)
})
}, [])
const onCheckSeries = (data) => {
@ -202,6 +200,7 @@ export default function PowerConditionalSelect(props) {
const param = {
pcsMkrCd: option.pcsMkrCd,
mixMatlNo: moduleSelectionData.module.mixMatlNo,
moduleMatlCds: moduleSelectionData.module.itemList.map((item) => item.itemId).join(','),
}
getPcsMakerList(param).then((res) => {

View File

@ -109,6 +109,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()) {
@ -164,6 +165,8 @@ export default function StepUp(props) {
targetModule.pcsItemId = module.pcsItemId
targetModule.circuitNumber = module.circuit
canvas.add(moduleCircuitText)
} else {
mChk++;
}
})
})
@ -172,6 +175,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') })
}
@ -467,7 +474,7 @@ export default function StepUp(props) {
module.pcsItemId = null
})
/** 선택된 모듈 목록 추가 */
/** 선택된 모듈 목록 추가 */
selectedData.roofSurfaceList.forEach((roofSurface) => {
const targetSurface = canvas.getObjects().filter((obj) => obj.id === roofSurface.roofSurfaceId)[0]
const moduleIds = targetSurface.modules.map((module) => {
@ -516,7 +523,7 @@ export default function StepUp(props) {
canvas.renderAll()
setModuleStatisticsData()
}
}
/**
* 현재 선택된 값들을 가져오는 함수 추가

View File

@ -12,6 +12,7 @@ import { selectedModuleState } from '@/store/selectedModuleOptions'
import { circuitNumDisplaySelector } from '@/store/settingAtom'
import { useContext, useEffect, useState } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import { normalizeDigits } from '@/util/input-utils'
export default function PassivityCircuitAllocation(props) {
const {
@ -580,7 +581,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,7 @@ 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'
export default function Eaves({ pitchRef, offsetRef, widthRef, radioTypeRef, pitchText }) {
const { getMessage } = useMessage()
@ -20,7 +21,17 @@ 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
}}
/>
</div>
<span className="thin">{pitchText}</span>
</div>
@ -29,7 +40,17 @@ 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
}}
/>
</div>
<span className="thin">mm</span>
</div>
@ -45,7 +66,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 +81,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 +91,27 @@ 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
}}
/>
</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

@ -37,7 +37,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 +52,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>
@ -74,7 +78,9 @@ export default function Gable({ pitchRef, offsetRef, widthRef, radioTypeRef, pit
</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>
@ -92,7 +98,9 @@ export default function Gable({ pitchRef, offsetRef, widthRef, radioTypeRef, pit
</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

@ -23,7 +23,9 @@ export default function WallMerge({ offsetRef, radioTypeRef }) {
</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>
@ -36,7 +38,9 @@ export default function WallMerge({ offsetRef, radioTypeRef }) {
</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>
@ -54,7 +58,9 @@ export default function WallMerge({ offsetRef, radioTypeRef }) {
</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

@ -8,6 +8,7 @@ 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',
@ -19,6 +20,7 @@ export default function FlowDirectionSetting(props) {
const { id, pos = contextPopupPosition, target } = props
const canvas = useRecoilValue(canvasState)
const { getMessage } = useMessage()
const { setSurfaceShapePattern } = useRoofFn()
const { changeSurfaceLineType } = useSurfaceShapeBatch({})
@ -32,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 },
]
@ -54,6 +81,7 @@ export default function FlowDirectionSetting(props) {
surfaceCompass: orientation,
surfaceCompassType: type,
})
setSurfaceShapePattern(roof, null, null, roof.roofMaterial)
drawDirectionArrow(roof)
canvas?.renderAll()
changeSurfaceLineType(roof)
@ -109,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)
@ -137,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

@ -5,9 +5,9 @@ import { useRecoilValue } from 'recoil'
import { contextPopupPositionState } from '@/store/popupAtom'
import { useState } from 'react'
import { canvasState, currentObjectState } from '@/store/canvasAtom'
import { useGrid } from '@/hooks/common/useGrid'
import { gridColorState } from '@/store/gridAtom'
import { gridDisplaySelector } from '@/store/settingAtom'
const GRID_PADDING = 5
export default function GridCopy(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
@ -25,10 +25,10 @@ export default function GridCopy(props) {
}
const copy = (object, length) => {
const lineStartX = object.direction === 'vertical' ? object.x1 + length : 0
const lineEndX = object.direction === 'vertical' ? object.x2 + length : canvas.width
const lineStartY = object.direction === 'vertical' ? 0 : object.y1 + length
const lineEndY = object.direction === 'vertical' ? canvas.width : object.y1 + 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,

View File

@ -3,11 +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 { 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)
@ -52,15 +51,15 @@ export default function GridMove(props) {
.forEach((grid) => {
move(
grid,
arrow2 === '←' ? (Number(horizonSize) * -1) / 10 : Number(horizonSize) / 10,
arrow1 === '↑' ? (Number(verticalSize) * -1) / 10 : Number(verticalSize) / 10,
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) / 10 : Number(horizonSize) / 10,
arrow1 === '↑' ? (Number(verticalSize) * -1) / 10 : Number(verticalSize) / 10,
arrow2 === '←' ? (Number(normalizeDigits(horizonSize)) * -1) / 10 : Number(normalizeDigits(horizonSize)) / 10,
arrow1 === '↑' ? (Number(normalizeDigits(verticalSize)) * -1) / 10 : Number(normalizeDigits(verticalSize)) / 10,
)
}
canvas.renderAll()
@ -70,10 +69,10 @@ export default function GridMove(props) {
const move = (object, x, y) => {
object.set({
...object,
x1: object.direction === 'vertical' ? object.x1 + x : 0,
x2: object.direction === 'vertical' ? object.x1 + x : canvas.width,
y1: object.direction === 'vertical' ? 0 : object.y1 + y,
y2: object.direction === 'vertical' ? canvas.height : object.y1 + y,
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,
})
}
@ -99,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>
@ -127,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,6 @@
import Image from 'next/image'
import { useMessage } from '@/hooks/useMessage'
import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/input-utils'
import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils'
export default function Angle({ props }) {
const { getMessage } = useMessage()
@ -20,7 +20,7 @@ export default function Angle({ props }) {
value={angle1}
ref={angle1Ref}
onFocus={(e) => (angle1Ref.current.value = '')}
onChange={(e) => onlyNumberWithDotInputChange(e, setAngle1)}
onChange={(e) => setAngle1(normalizeDecimalLimit(e.target.value, 2))}
placeholder="45"
/>
</div>
@ -40,7 +40,7 @@ export default function Angle({ props }) {
value={length1}
ref={length1Ref}
onFocus={(e) => (length1Ref.current.value = '')}
onChange={(e) => onlyNumberInputChange(e, setLength1)}
onChange={(e) => setLength1(normalizeDigits(e.target.value))}
placeholder="3000"
/>
</div>

View File

@ -1,5 +1,5 @@
import { useMessage } from '@/hooks/useMessage'
import { onlyNumberInputChange } from '@/util/input-utils'
import { normalizeDigits } from '@/util/input-utils'
export default function Diagonal({ props }) {
const { getMessage } = useMessage()
@ -36,7 +36,7 @@ export default function Diagonal({ props }) {
value={outerLineDiagonalLength}
ref={outerLineDiagonalLengthRef}
onFocus={(e) => (outerLineDiagonalLengthRef.current.value = '')}
onChange={(e) => onlyNumberInputChange(e, setOuterLineDiagonalLength)}
onChange={(e) => setOuterLineDiagonalLength(normalizeDigits(e.target.value))}
placeholder="3000"
/>
</div>
@ -58,7 +58,7 @@ export default function Diagonal({ props }) {
value={length1}
ref={length1Ref}
onFocus={(e) => (length1Ref.current.value = '')}
onChange={(e) => onlyNumberInputChange(e, setLength1)}
onChange={(e) => setLength1(normalizeDigits(e.target.value))}
placeholder="3000"
/>
</div>
@ -115,7 +115,7 @@ export default function Diagonal({ props }) {
className="input-origin block"
value={length2}
ref={length2Ref}
onChange={(e) => onlyNumberInputChange(e, setLength2)}
onChange={(e) => setLength2(normalizeDigits(e.target.value))}
readOnly={true}
placeholder="3000"
/>

View File

@ -1,5 +1,5 @@
import { useMessage } from '@/hooks/useMessage'
import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/input-utils'
import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils'
import { getDegreeByChon } from '@/util/canvas-util'
export default function DoublePitch({ props }) {
@ -56,7 +56,7 @@ export default function DoublePitch({ props }) {
value={angle1}
ref={angle1Ref}
onFocus={(e) => (angle1Ref.current.value = '')}
onChange={(e) => onlyNumberWithDotInputChange(e, setAngle1)}
onChange={(e) => setAngle1(normalizeDecimalLimit(e.target.value, 2))}
placeholder="45"
/>
</div>
@ -73,7 +73,7 @@ export default function DoublePitch({ props }) {
value={length1}
ref={length1Ref}
onFocus={(e) => (length1Ref.current.value = '')}
onChange={(e) => onlyNumberInputChange(e, setLength1)}
onChange={(e) => setLength1(normalizeDigits(e.target.value))}
placeholder="3000"
/>
</div>
@ -132,7 +132,8 @@ export default function DoublePitch({ props }) {
ref={angle2Ref}
onFocus={(e) => (angle2Ref.current.value = '')}
onChange={(e) => {
onlyNumberWithDotInputChange(e, setAngle2)
const v = normalizeDecimalLimit(e.target.value, 2)
setAngle2(v)
setLength2(getLength2())
}}
placeholder="45"
@ -156,7 +157,7 @@ export default function DoublePitch({ props }) {
value={length2}
ref={length2Ref}
onFocus={(e) => (length2Ref.current.value = '')}
onChange={(e) => onlyNumberInputChange(e, setLength2)}
onChange={(e) => setLength2(normalizeDigits(e.target.value))}
readOnly={true}
placeholder="3000"
/>

View File

@ -1,30 +1,91 @@
'use client'
import { useMessage } from '@/hooks/useMessage'
import { onlyNumberInputChange } from '@/util/input-utils'
import { normalizeDigits } from '@/util/input-utils'
import { getChonByDegree, getDegreeByChon } from '@/util/canvas-util'
import { CalculatorInput } from '@/components/common/input/CalcInput'
import { useEffect, useRef } from 'react'
export default function OuterLineWall({ props }) {
const { getMessage } = useMessage()
const { length1, setLength1, length1Ref, arrow1, setArrow1 } = props
//
useEffect(() => {
const handleKeyDown = (e) => {
//
const keypadVisible = document.querySelector('.keypad-container')
//
if (keypadVisible) {
return
}
// input
if (document.activeElement && document.activeElement.classList.contains('calculator-input')) {
return
}
//
if (e.key === 'Enter') {
// (useOuterLineWall.js )
return
}
// input
if (/^[0-9+\-*\/=.]$/.test(e.key) || e.key === 'Backspace' || e.key === 'Delete') {
const calcInput = document.querySelector('.calculator-input')
if (calcInput) {
// preventDefault
calcInput.focus()
calcInput.click()
}
}
}
// capture: true
document.addEventListener('keydown', handleKeyDown, { capture: true })
return () => document.removeEventListener('keydown', handleKeyDown, { capture: true })
}, [])
return (
<div className="outline-wrap">
<div className="outline-inner">
<div className="outline-form">
<span className="mr10">{getMessage('straight.line')}</span>
<div className="input-grid" style={{ width: '63px' }}>
<input
type="text"
className="input-origin block"
{/*<input*/}
{/* type="text"*/}
{/* className="input-origin block"*/}
{/* value={length1}*/}
{/* ref={length1Ref}*/}
{/* onFocus={(e) => {*/}
{/* if (length1Ref.current.value === '0') {*/}
{/* length1Ref.current.value = ''*/}
{/* }*/}
{/* }}*/}
{/* onChange={(e) => setLength1(normalizeDigits(e.target.value))}*/}
{/* placeholder="3000"*/}
{/*/>*/}
<CalculatorInput
id="length1-calc"
label=""
className="input-origin block calculator-input"
readOnly={false}
value={length1}
onChange={(value) => setLength1(value)}
options={{
allowNegative: false,
allowDecimal: false //(index !== 0),
}}
placeholder={'3000'}
ref={length1Ref}
onFocus={(e) => {
if (length1Ref.current.value === '0') {
onFocus={() => {
if (length1Ref.current && length1Ref.current.value === '0') {
length1Ref.current.value = ''
}
}}
onChange={(e) => onlyNumberInputChange(e, setLength1)}
placeholder="3000"
/>
</div>
<button className="reset-btn" onClick={() => setLength1(0)}></button>

View File

@ -1,9 +1,21 @@
import { useMessage } from '@/hooks/useMessage'
import { onlyNumberInputChange } from '@/util/input-utils'
import { normalizeDigits } from '@/util/input-utils'
export default function RightAngle({ props }) {
const { getMessage } = useMessage()
const { length1, setLength1, length1Ref, length2, setLength2, length2Ref, arrow1, setArrow1, arrow2, setArrow2 } = props
const handleClickArrow = (arrow) => {
const arrowType = arrow === '↑' ? 'ArrowUp' : arrow === '↓' ? 'ArrowDown' : arrow === '←' ? 'ArrowLeft' : arrow === '→' ? 'ArrowRight' : ''
setArrow2(arrow)
const originLeng2Val = length2Ref.current.value
if (originLeng2Val === '') {
length2Ref.current.value = '0'
}
length2Ref.current.focus()
length2Ref.current.value = originLeng2Val
document.dispatchEvent(new KeyboardEvent('keydown', { key: arrowType }))
}
return (
<div className="outline-wrap">
<div className="outline-inner">
@ -16,7 +28,7 @@ export default function RightAngle({ props }) {
value={length1}
ref={length1Ref}
onFocus={(e) => (length1Ref.current.value = '')}
onChange={(e) => onlyNumberInputChange(e, setLength1)}
onChange={(e) => setLength1(normalizeDigits(e.target.value))}
placeholder="3000"
/>
</div>
@ -72,7 +84,7 @@ export default function RightAngle({ props }) {
value={length2}
ref={length2Ref}
onFocus={(e) => (length2Ref.current.value = '')}
onChange={(e) => onlyNumberInputChange(e, setLength2)}
onChange={(e) => setLength2(normalizeDigits(e.target.value))}
placeholder="3000"
/>
</div>
@ -89,29 +101,25 @@ export default function RightAngle({ props }) {
<button
className={`direction up ${arrow2 === '↑' ? 'act' : ''}`}
onClick={() => {
setArrow2('↑')
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' }))
handleClickArrow('↑')
}}
></button>
<button
className={`direction down ${arrow2 === '↓' ? 'act' : ''}`}
onClick={() => {
setArrow2('↓')
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }))
handleClickArrow('↓')
}}
></button>
<button
className={`direction left ${arrow2 === '←' ? 'act' : ''}`}
onClick={() => {
setArrow2('←')
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft' }))
handleClickArrow('←')
}}
></button>
<button
className={`direction right ${arrow2 === '→' ? 'act' : ''}`}
onClick={() => {
setArrow2('→')
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }))
handleClickArrow('→')
}}
></button>
</div>

View File

@ -4,10 +4,7 @@ import { contextPopupPositionState } from '@/store/popupAtom'
import { usePopup } from '@/hooks/usePopup'
import { useMessage } from '@/hooks/useMessage'
import { useEffect, useState } from 'react'
import { polygonToTurfPolygon } from '@/util/canvas-util'
import { deepCopyArray } from '@/util/common-utils'
import { canvasState } from '@/store/canvasAtom'
import * as turf from '@turf/turf'
import { POLYGON_TYPE } from '@/common/common'
import { useModule } from '@/hooks/module/useModule'
import { useSwal } from '@/hooks/useSwal'
@ -41,7 +38,15 @@ export default function PanelEdit(props) {
//
return () => {
const modules = canvas.getObjects().filter((obj) => obj.name === 'module')
modules.forEach((obj) => {
obj.set({
stroke: 'black',
strokeWidth: 0.3,
})
})
canvas?.discardActiveObject() //
canvas.renderAll()
}
}, [])

View File

@ -12,11 +12,12 @@ export default function FlowLine({ FLOW_LINE_REF }) {
const { getMessage } = useMessage()
const [type, setType] = useState(FLOW_LINE_TYPE.DOWN_LEFT)
const [filledInput, setFilledInput] = useState('')
const [pointerInput, setPointerInput] = useState('100')
const currentObject = useRecoilValue(currentObjectState)
const handleFocus = () => {
if (currentObject === null) {
FLOW_LINE_REF.POINTER_INPUT_REF.current.value = ''
FLOW_LINE_REF.FILLED_INPUT_REF.current.value = ''
setPointerInput('')
setFilledInput('')
FLOW_LINE_REF.FILLED_INPUT_REF.current.blur()
}
}
@ -35,7 +36,7 @@ export default function FlowLine({ FLOW_LINE_REF }) {
<div className="outline-form">
<span>{getMessage('modal.movement.flow.line.position')}</span>
<div className="input-grid mr5">
<input type="text" className="input-origin block" defaultValue={100} readOnly={true} ref={FLOW_LINE_REF.POINTER_INPUT_REF} />
<input type="text" className="input-origin block" value={pointerInput} readOnly={true} ref={FLOW_LINE_REF.POINTER_INPUT_REF} />
</div>
</div>
<div className="moving-tab-content">
@ -71,7 +72,6 @@ export default function FlowLine({ FLOW_LINE_REF }) {
<input
type="text"
className="input-origin block"
defaultValue={100}
ref={FLOW_LINE_REF.FILLED_INPUT_REF}
value={filledInput}
onFocus={handleFocus}

View File

@ -2,6 +2,7 @@ import { useMessage } from '@/hooks/useMessage'
import { useState } from 'react'
import { useRecoilValue } from 'recoil'
import { currentObjectState } from '@/store/canvasAtom'
import { normalizeDigits } from '@/util/input-utils'
const UP_DOWN_TYPE = {
UP: 'up',
@ -11,7 +12,7 @@ const UP_DOWN_TYPE = {
export default function Updown({ UP_DOWN_REF }) {
const { getMessage } = useMessage()
const [type, setType] = useState(UP_DOWN_TYPE.UP)
const [filledInput, setFilledInput] = useState('')
const [filledInput, setFilledInput] = useState('100')
const currentObject = useRecoilValue(currentObjectState)
const handleFocus = () => {
if (currentObject === null) {
@ -22,7 +23,8 @@ export default function Updown({ UP_DOWN_REF }) {
}
const handleInput = (e) => {
const value = e.target.value.replace(/^0+/, '')
setFilledInput(value.replace(/[^0-9]/g, ''))
//setFilledInput(value.replace(/[^0-9]/g, ''))
setFilledInput(normalizeDigits(value))
}
return (
@ -69,7 +71,6 @@ export default function Updown({ UP_DOWN_REF }) {
<input
type="text"
className="input-origin block"
defaultValue={100}
ref={UP_DOWN_REF.FILLED_INPUT_REF}
value={filledInput}
onFocus={handleFocus}

View File

@ -13,6 +13,7 @@ import Shadow from '@/components/floor-plan/modal/object/type/Shadow'
import TriangleDormer from '@/components/floor-plan/modal/object/type/TriangleDormer'
import PentagonDormer from '@/components/floor-plan/modal/object/type/PentagonDormer'
import { usePopup } from '@/hooks/usePopup'
import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils'
export default function ObjectSetting({ id, pos = { x: 50, y: 230 } }) {
const { getMessage } = useMessage()
@ -61,20 +62,20 @@ export default function ObjectSetting({ id, pos = { x: 50, y: 230 } }) {
if (buttonAct === 1 || buttonAct === 2) {
applyOpeningAndShadow(objectPlacement, buttonAct)
} else {
const height = dormerPlacement.heightRef.current !== null ? dormerPlacement.heightRef.current.value / 10 : 0
const width = dormerPlacement.widthRef.current !== null ? dormerPlacement.widthRef.current.value / 10 : 0 //triangle
const pitch = dormerPlacement.pitchRef.current !== null ? Number(dormerPlacement.pitchRef.current.value) : 0
const height = dormerPlacement.heightRef.current !== null ? Number(normalizeDigits(dormerPlacement.heightRef.current.value)) / 10 : 0
const width = dormerPlacement.widthRef.current !== null ? Number(normalizeDigits(dormerPlacement.widthRef.current.value)) / 10 : 0 //triangle
const pitch = dormerPlacement.pitchRef.current !== null ? Number(normalizeDecimalLimit(dormerPlacement.pitchRef.current.value, 2)) : 0
const offsetRef =
dormerPlacement.offsetRef.current !== null
? dormerPlacement.offsetRef.current.value === ''
? 0
: parseInt(dormerPlacement.offsetRef.current.value) / 10
: Number(normalizeDigits(dormerPlacement.offsetRef.current.value)) / 10
: 0
const offsetWidthRef =
dormerPlacement.offsetWidthRef.current !== null
? dormerPlacement.offsetWidthRef.current.value === ''
? 0
: parseInt(dormerPlacement.offsetWidthRef.current.value) / 10
: Number(normalizeDigits(dormerPlacement.offsetWidthRef.current.value)) / 10
: 0
const directionRef = dormerPlacement.directionRef.current

View File

@ -12,7 +12,7 @@ import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch'
export default function SizeSetting(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
const [settingTarget, setSettingTarget] = useState(1)
const [settingTarget, setSettingTarget] = useState(props.side || 1)
const { id, pos = contextPopupPosition, target } = props
const { getMessage } = useMessage()
const { closePopup } = usePopup()
@ -47,11 +47,11 @@ export default function SizeSetting(props) {
<div className="size-option-top">
<div className="size-option-wrap">
<div className="size-option mb5">
<input type="text" className="input-origin mr5" value={target?.width.toFixed(0) * 10} readOnly={true} />
<input type="text" className="input-origin mr5" value={(target?.originWidth * 10).toFixed(0)} readOnly={true} />
<span className="normal-font">mm</span>
</div>
<div className="size-option">
<input type="text" className="input-origin mr5" defaultValue={target?.width.toFixed(0) * 10} ref={widthRef} />
<input type="text" className="input-origin mr5" defaultValue={(target?.originWidth * 10).toFixed(0)} ref={widthRef} />
<span className="normal-font">mm</span>
</div>
</div>
@ -60,11 +60,11 @@ export default function SizeSetting(props) {
<div className="size-option-side">
<div className="size-option-wrap">
<div className="size-option mb5">
<input type="text" className="input-origin mr5" value={target?.height.toFixed(0) * 10} readOnly={true} />
<input type="text" className="input-origin mr5" value={(target?.originHeight * 10).toFixed(0)} readOnly={true} />
<span className="normal-font">mm</span>
</div>
<div className="size-option">
<input type="text" className="input-origin mr5" defaultValue={target?.height.toFixed(0) * 10} ref={heightRef} />
<input type="text" className="input-origin mr5" defaultValue={(target?.originHeight * 10).toFixed(0)} ref={heightRef} />
<span className="normal-font">mm</span>
</div>
</div>

View File

@ -14,12 +14,14 @@ import { useCommonCode } from '@/hooks/common/useCommonCode'
import QSelectBox from '@/components/common/select/QSelectBox'
import { globalLocaleStore } from '@/store/localeAtom'
import { onlyNumberInputChange } from '@/util/input-utils'
import { getChonByDegree, getDegreeByChon } from '@/util/canvas-util'
import { usePolygon } from '@/hooks/usePolygon'
import { canvasState } from '@/store/canvasAtom'
import { useRoofFn } from '@/hooks/common/useRoofFn'
import { usePlan } from '@/hooks/usePlan'
import { normalizeDecimal, normalizeDigits } from '@/util/input-utils'
import { logger } from '@/util/logger'
import { CalculatorInput } from '@/components/common/input/CalcInput'
/**
* 지붕 레이아웃
@ -182,7 +184,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
*/
const changeInput = (value, e) => {
const { name } = e.target
setCurrentRoof({ ...currentRoof, [name]: Number(value) })
setCurrentRoof({ ...currentRoof, [name]: Number(normalizeDecimal(value)) })
}
/**
@ -207,6 +209,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
* 배치면초기설정 저장 버튼 클릭
*/
const handleSaveBtn = async () => {
const roofInfo = {
...currentRoof,
planNo: basicSetting.planNo,
@ -214,7 +217,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
width: roofRef.width.current?.value,
length: roofRef.length.current?.value,
hajebichi: roofRef.hajebichi.current?.value,
raft: roofRef.rafter.current?.value,
//raft: roofRef.rafter.current?.value,
selected: true,
layout: currentRoof.layout,
index: 0,
@ -224,7 +227,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
newAddedRoofs[0] = { ...roofInfo }
setAddedRoofs(newAddedRoofs)
console.log('save Info', {
logger.debug('save Info', {
...basicSetting,
selectedRoofMaterial: {
...newAddedRoofs[0],
@ -324,16 +327,51 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
</div>
</span>
<div className="input-grid mr5">
<input
type="number"
{/* <input
type="text"
className="input-origin block"
readOnly={currentRoof?.roofAngleSet !== item.value}
value={index === 0 ? currentRoof?.pitch : currentRoof?.angle}
onChange={(e) =>
index === 0
? setCurrentRoof({ ...currentRoof, pitch: e.target.value, angle: getDegreeByChon(e.target.value) })
: setCurrentRoof({ ...currentRoof, pitch: getChonByDegree(e.target.value), angle: e.target.value })
}
value={index === 0 ? currentRoof?.pitch || '0' : currentRoof?.angle || '0'}
onChange={(e) => {
const v = normalizeDecimal(e.target.value)
e.target.value = v
if (index === 0) {
const num = v === '' ? '' : Number(v)
setCurrentRoof({ ...currentRoof, pitch: num === '' ? '' : num, angle: num === '' ? '' : getDegreeByChon(num) })
} else {
const num = v === '' ? '' : Number(v)
setCurrentRoof({ ...currentRoof, pitch: num === '' ? '' : getChonByDegree(num), angle: num === '' ? '' : num })
}
}}
/> */}
<CalculatorInput
id=""
name=""
label=""
className="input-origin block"
readOnly={currentRoof?.roofAngleSet !== item.value}
value={index === 0 ? currentRoof?.pitch || '0' : currentRoof?.angle || '0'}
onChange={(value) => {
if (index === 0) {
const num = value === '' ? '' : Number(value)
setCurrentRoof(prev => ({
...prev,
pitch: num === '' ? '' : num,
angle: num === '' ? '' : getDegreeByChon(num),
}))
} else {
const num = value === '' ? '' : Number(value)
setCurrentRoof( prev => ({
...prev,
pitch: num === '' ? '' : getChonByDegree(num),
angle: num === '' ? '' : num,
}))
}
}}
options={{
allowNegative: false,
allowDecimal: true //(index !== 0),
}}
/>
</div>
<span className="thin">{index === 0 ? '寸' : '度'}</span>
@ -375,15 +413,33 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
<div className="flex-ment">
<span>W</span>
<div className="input-grid" style={{ width: '84px' }}>
<input
type="text"
{/*<input*/}
{/* type="text"*/}
{/* className="input-origin block"*/}
{/* name={`width`}*/}
{/* ref={roofRef.width}*/}
{/* value={parseInt(currentRoof?.width)}*/}
{/* onChange={(e) => changeInput(normalizeDigits(e.target.value), e)}*/}
{/* readOnly={currentRoof?.widAuth === 'R'}*/}
{/* disabled={currentRoof?.roofSizeSet === '3'}*/}
{/*/>*/}
<CalculatorInput
id=""
name={'width'}
label=""
className="input-origin block"
name={`width`}
ref={roofRef.width}
value={parseInt(currentRoof?.width)}
onChange={(e) => onlyNumberInputChange(e, changeInput)}
value={currentRoof?.width||0}
onChange={(value) => {
setCurrentRoof({ ...currentRoof, value })
}}
readOnly={currentRoof?.widAuth === 'R'}
disabled={currentRoof?.roofSizeSet === '3'}
options={{
allowNegative: false,
allowDecimal: false //(index !== 0),
}}
/>
</div>
</div>
@ -392,15 +448,33 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
<div className="flex-ment">
<span>L</span>
<div className="input-grid" style={{ width: '84px' }}>
<input
type="text"
{/*<input*/}
{/* type="text"*/}
{/* className="input-origin block"*/}
{/* name={`length`}*/}
{/* ref={roofRef.length}*/}
{/* value={parseInt(currentRoof?.length)}*/}
{/* onChange={(e) => changeInput(normalizeDigits(e.target.value), e)}*/}
{/* readOnly={currentRoof?.lenAuth === 'R'}*/}
{/* disabled={currentRoof?.roofSizeSet === '3'}*/}
{/*/>*/}
<CalculatorInput
id=""
name={'length'}
label=""
className="input-origin block"
name={`length`}
ref={roofRef.length}
value={parseInt(currentRoof?.length)}
onChange={(e) => onlyNumberInputChange(e, changeInput)}
value={currentRoof?.length||0}
onChange={(value) => {
setCurrentRoof({ ...currentRoof, value })
}}
readOnly={currentRoof?.lenAuth === 'R'}
disabled={currentRoof?.roofSizeSet === '3'}
options={{
allowNegative: false,
allowDecimal: false //(index !== 0),
}}
/>
</div>
</div>
@ -412,11 +486,8 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
<div className="select-wrap" style={{ width: '160px' }}>
<QSelectBox
options={raftCodes}
title={
raftCodes?.find((r) => r.clCode === (currentRoof?.raft === undefined ? currentRoof?.raftBaseCd : currentRoof?.raft))
.clCodeNm
}
value={currentRoof?.raft === undefined ? currentRoof?.raftBaseCd : currentRoof?.raft}
title={raftCodes?.find((r) => r.clCode === (currentRoof.raft ?? currentRoof?.raftBaseCd))?.clCodeNm}
value={currentRoof?.raft ?? currentRoof?.raftBaseCd}
onChange={(e) => handleRafterChange(e.clCode)}
sourceKey="clCode"
targetKey={currentRoof?.raft ? 'raft' : 'raftBaseCd'}
@ -431,16 +502,34 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
<div className="flex-ment">
<span>{getMessage('hajebichi')}</span>
<div className="input-grid" style={{ width: '84px' }}>
<input
type="text"
{/*<input*/}
{/* type="text"*/}
{/* className="input-origin block"*/}
{/* name={`hajebichi`}*/}
{/* ref={roofRef.hajebichi}*/}
{/* value={parseInt(currentRoof?.hajebichi)}*/}
{/* onChange={(e) => changeInput(normalizeDigits(e.target.value), e)}*/}
{/* readOnly={currentRoof?.roofPchAuth === 'R'}*/}
{/* disabled={currentRoof?.roofSizeSet === '3'}*/}
{/*/>*/}
<CalculatorInput
id=""
name={'hajebichi'}
label=""
className="input-origin block"
name={`hajebichi`}
ref={roofRef.hajebichi}
value={parseInt(currentRoof?.hajebichi)}
onChange={(e) => onlyNumberInputChange(e, changeInput)}
value={currentRoof?.hajebichi||0}
onChange={(value) => {
setCurrentRoof({ ...currentRoof, value })
}}
readOnly={currentRoof?.roofPchAuth === 'R'}
disabled={currentRoof?.roofSizeSet === '3'}
options={{
allowNegative: false,
allowDecimal: false //(index !== 0),
}}
/>
</div>
</div>
)}

View File

@ -7,6 +7,7 @@ import { useEffect, useState } from 'react'
import { currentObjectState } from '@/store/canvasAtom'
import { useRoofAllocationSetting } from '@/hooks/roofcover/useRoofAllocationSetting'
import { useSwal } from '@/hooks/useSwal'
import { normalizeDigits } from '@/util/input-utils'
export default function ActualSizeSetting(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
@ -27,7 +28,7 @@ export default function ActualSizeSetting(props) {
const handleFinish = () => {
swalFire({
text: '완료 하시겠습니까?',
text: getMessage("modal.roof.allocation.auxiliary.accept"),
type: 'confirm',
confirmFn: () => {
handleAlloc()
@ -37,7 +38,7 @@ export default function ActualSizeSetting(props) {
const handleClose = () => {
swalFire({
text: '완료 하시겠습니까?',
text: getMessage("modal.roof.allocation.auxiliary.accept"),
type: 'confirm',
confirmFn: () => {
handleAlloc()
@ -87,7 +88,7 @@ export default function ActualSizeSetting(props) {
<div className="eaves-keraba-td">
<div className="outline-form">
<div className="input-grid mr5">
<input type="text" className="input-origin block" value={actualSize} onChange={(e) => setActualSize(Number(e.target.value))} />
<input type="text" className="input-origin block" value={actualSize} onChange={(e) => setActualSize(Number(normalizeDigits(e.target.value)))} />
</div>
<span className="thin">mm</span>
</div>

View File

@ -12,6 +12,7 @@ import { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
import { useCommonCode } from '@/hooks/common/useCommonCode'
import { globalLocaleStore } from '@/store/localeAtom'
import { currentAngleTypeSelector, pitchTextSelector } from '@/store/canvasAtom'
import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils'
export default function ContextRoofAllocationSetting(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
@ -85,7 +86,7 @@ export default function ContextRoofAllocationSetting(props) {
return (
<div className="grid-option-box" key={index}>
<div className="d-check-radio pop no-text">
<input type="radio" name="radio01" checked={roof.selected && 'checked'} readOnly={true} />
<input type="radio" name="radio01" checked={!!roof.selected} readOnly={true} />
<label
htmlFor="ra01"
onClick={(e) => {
@ -151,6 +152,7 @@ export default function ContextRoofAllocationSetting(props) {
defaultValue={roof.width}
readOnly={roof.widAuth === 'R'}
onChange={(e) => {
e.target.value = normalizeDigits(e.target.value)
handleChangeInput(e, 'width', index)
}}
/>
@ -169,6 +171,7 @@ export default function ContextRoofAllocationSetting(props) {
defaultValue={roof.length}
readOnly={roof.lenAuth === 'R'}
onChange={(e) => {
e.target.value = normalizeDigits(e.target.value)
handleChangeInput(e, 'length', index)
}}
/>
@ -186,9 +189,12 @@ export default function ContextRoofAllocationSetting(props) {
<input
type="text"
className="input-origin block"
value={parseInt(roof.hajebichi)}
value={roof.hajebichi ?? ''}
readOnly={roof.roofPchAuth === 'R'}
onChange={(e) => handleChangeInput(e, 'hajebichi', index)}
onChange={(e) => {
e.target.value = normalizeDigits(e.target.value)
handleChangeInput(e, 'hajebichi', index)
}}
/>
</div>
</div>
@ -202,11 +208,10 @@ export default function ContextRoofAllocationSetting(props) {
type="text"
className="input-origin block"
onChange={(e) => {
// handleChangeInput(e, currentAngleType === 'slope' ? 'pitch' : 'angle', index)
e.target.value = normalizeDecimalLimit(e.target.value, 2)
handleChangePitch(e, index)
}}
value={currentAngleType === 'slope' ? roof.pitch : roof.angle}
defaultValue={currentAngleType === 'slope' ? roof.pitch : roof.angle}
value={currentAngleType === 'slope' ? (roof.pitch ?? '') : (roof.angle ?? '')}
/>
</div>
<span className="absol">{pitchText}</span>

View File

@ -13,7 +13,7 @@ import { globalLocaleStore } from '@/store/localeAtom'
import { useRoofShapeSetting } from '@/hooks/roofcover/useRoofShapeSetting'
import { currentAngleTypeSelector, pitchTextSelector } from '@/store/canvasAtom'
import { getDegreeByChon } from '@/util/canvas-util'
import { onlyNumberWithDotInputChange } from '@/util/input-utils'
import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils'
export default function RoofAllocationSetting(props) {
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
@ -86,7 +86,7 @@ export default function RoofAllocationSetting(props) {
return (
<div className="grid-option-box" key={index}>
<div className="d-check-radio pop no-text">
<input type="radio" name="radio01" checked={roof.selected && 'checked'} readOnly={true} />
<input type="radio" name="radio01" checked={!!roof.selected} readOnly />
<label
htmlFor="ra01"
onClick={(e) => {
@ -151,7 +151,10 @@ export default function RoofAllocationSetting(props) {
type="text"
className="input-origin block"
defaultValue={roof.width}
onChange={(e) => handleChangeInput(e, 'width', index)}
onChange={(e) => {
e.target.value = normalizeDigits(e.target.value)
handleChangeInput(e, 'width', index)
}}
readOnly={roof.widAuth === 'R'}
/>
</div>
@ -167,7 +170,10 @@ export default function RoofAllocationSetting(props) {
type="text"
className="input-origin block"
defaultValue={roof.length}
onChange={(e) => handleChangeInput(e, 'length', index)}
onChange={(e) => {
e.target.value = normalizeDigits(e.target.value)
handleChangeInput(e, 'length', index)
}}
readOnly={roof.lenAuth === 'R'}
/>
</div>
@ -184,8 +190,11 @@ export default function RoofAllocationSetting(props) {
<input
type="text"
className="input-origin block"
onChange={(e) => handleChangeInput(e, 'hajebichi', index)}
value={parseInt(roof.hajebichi)}
onChange={(e) => {
e.target.value = normalizeDigits(e.target.value)
handleChangeInput(e, 'hajebichi', index)
}}
value={roof.hajebichi ?? ''}
readOnly={roof.roofPchAuth === 'R'}
/>
</div>
@ -200,10 +209,10 @@ export default function RoofAllocationSetting(props) {
type="text"
className="input-origin block"
onChange={(e) => {
e.target.value = normalizeDecimalLimit(e.target.value, 2)
handleChangePitch(e, index)
}}
value={currentAngleType === 'slope' ? roof.pitch : roof.angle}
defaultValue={currentAngleType === 'slope' ? roof.pitch : roof.angle}
value={currentAngleType === 'slope' ? (roof.pitch ?? '') : (roof.angle ?? '')}
/>
</div>
<span className="absol">{pitchText}</span>

View File

@ -1,5 +1,5 @@
import { useMessage } from '@/hooks/useMessage'
import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/input-utils'
import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils'
export default function Direction({ pitch, setPitch, eavesOffset, setEavesOffset, gableOffset, setGableOffset, shedWidth, setShedWidth, pitchText }) {
const { getMessage } = useMessage()
@ -10,7 +10,12 @@ export default function Direction({ pitch, setPitch, eavesOffset, setEavesOffset
{getMessage('slope')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={pitch} onChange={(e) => onlyNumberWithDotInputChange(e, setPitch)} />
<input
type="text"
className="input-origin block"
value={pitch}
onChange={(e) => setPitch(normalizeDecimalLimit(e.target.value, 2))}
/>
</div>
<span className="thin">{pitchText}</span>
</div>
@ -19,7 +24,12 @@ export default function Direction({ pitch, setPitch, eavesOffset, setEavesOffset
{getMessage('eaves.offset')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={eavesOffset} onChange={(e) => onlyNumberInputChange(e, setEavesOffset)} />
<input
type="text"
className="input-origin block"
value={eavesOffset}
onChange={(e) => setEavesOffset(normalizeDigits(e.target.value))}
/>
</div>
<span className="thin">mm</span>
</div>
@ -28,7 +38,12 @@ export default function Direction({ pitch, setPitch, eavesOffset, setEavesOffset
{getMessage('gable.offset')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={gableOffset} onChange={(e) => onlyNumberInputChange(e, setGableOffset)} />
<input
type="text"
className="input-origin block"
value={gableOffset}
onChange={(e) => setGableOffset(normalizeDigits(e.target.value))}
/>
</div>
<span className="thin">mm</span>
</div>
@ -37,7 +52,12 @@ export default function Direction({ pitch, setPitch, eavesOffset, setEavesOffset
{getMessage('windage.width')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={shedWidth} onChange={(e) => onlyNumberInputChange(e, setShedWidth)} />
<input
type="text"
className="input-origin block"
value={shedWidth}
onChange={(e) => setShedWidth(normalizeDigits(e.target.value))}
/>
</div>
<span className="thin">mm</span>
</div>

View File

@ -1,5 +1,5 @@
import { useMessage } from '@/hooks/useMessage'
import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/input-utils'
import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils'
export default function Pattern(props) {
const { getMessage } = useMessage()
@ -11,7 +11,7 @@ export default function Pattern(props) {
{getMessage('slope')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={pitch} onChange={(e) => onlyNumberWithDotInputChange(e, setPitch)} />
<input type="text" className="input-origin block" value={pitch} onChange={(e) => setPitch(normalizeDecimalLimit(e.target.value, 2))} />
</div>
<span className="thin"> {pitchText}</span>
</div>
@ -20,7 +20,7 @@ export default function Pattern(props) {
{getMessage('eaves.offset')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={eavesOffset} onChange={(e) => onlyNumberInputChange(e, setEavesOffset)} />
<input type="text" className="input-origin block" value={eavesOffset} onChange={(e) => setEavesOffset(normalizeDigits(e.target.value))} />
</div>
<span className="thin">mm</span>
</div>
@ -29,7 +29,7 @@ export default function Pattern(props) {
{getMessage('gable.offset')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={gableOffset} onChange={(e) => onlyNumberInputChange(e, setGableOffset)} />
<input type="text" className="input-origin block" value={gableOffset} onChange={(e) => setGableOffset(normalizeDigits(e.target.value))} />
</div>
<span className="thin">mm</span>
</div>

View File

@ -1,6 +1,6 @@
import { useMessage } from '@/hooks/useMessage'
import { useEffect } from 'react'
import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/input-utils'
import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils'
export default function Ridge(props) {
const { getMessage } = useMessage()
@ -13,7 +13,7 @@ export default function Ridge(props) {
{getMessage('slope')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={pitch} onChange={(e) => onlyNumberWithDotInputChange(e, setPitch)} />
<input type="text" className="input-origin block" value={pitch} onChange={(e) => setPitch(normalizeDecimalLimit(e.target.value, 2))} />
</div>
<span className="thin">{pitchText}</span>
</div>
@ -22,7 +22,7 @@ export default function Ridge(props) {
{getMessage('eaves.offset')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={eavesOffset} onChange={(e) => onlyNumberInputChange(e, setEavesOffset)} />
<input type="text" className="input-origin block" value={eavesOffset} onChange={(e) => setEavesOffset(normalizeDigits(e.target.value))} />
</div>
<span className="thin">mm</span>
</div>

View File

@ -1,5 +1,5 @@
import { useMessage } from '@/hooks/useMessage'
import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/input-utils'
import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils'
export default function Eaves({ pitch, setPitch, eavesOffset, setEavesOffset, pitchText }) {
const { getMessage } = useMessage()
@ -10,7 +10,7 @@ export default function Eaves({ pitch, setPitch, eavesOffset, setEavesOffset, pi
{getMessage('slope')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={pitch} onChange={(e) => onlyNumberWithDotInputChange(e, setPitch)} />
<input type="text" className="input-origin block" value={pitch} onChange={(e) => setPitch(normalizeDecimalLimit(e.target.value, 2))} />
</div>
<span className="thin">{pitchText}</span>
</div>
@ -19,7 +19,7 @@ export default function Eaves({ pitch, setPitch, eavesOffset, setEavesOffset, pi
{getMessage('eaves.offset')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={eavesOffset} onChange={(e) => onlyNumberInputChange(e, setEavesOffset)} />
<input type="text" className="input-origin block" value={eavesOffset} onChange={(e) => setEavesOffset(normalizeDigits(e.target.value))} />
</div>
<span className="thin">mm</span>
</div>

View File

@ -1,6 +1,6 @@
import { useMessage } from '@/hooks/useMessage'
import { useEffect } from 'react'
import { onlyNumberInputChange } from '@/util/input-utils'
import { normalizeDigits } from '@/util/input-utils'
export default function Gable({ gableOffset, setGableOffset }) {
const { getMessage } = useMessage()
@ -10,7 +10,7 @@ export default function Gable({ gableOffset, setGableOffset }) {
<div className="outline-form">
<span className="mr10">{getMessage('gable.offset')}</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={gableOffset} onChange={(e) => onlyNumberInputChange(e, setGableOffset)} />
<input type="text" className="input-origin block" value={gableOffset} onChange={(e) => setGableOffset(normalizeDigits(e.target.value))} />
</div>
<span className="thin">mm</span>
</div>

View File

@ -1,5 +1,5 @@
import { useMessage } from '@/hooks/useMessage'
import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/input-utils'
import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils'
export default function HipAndGable({ pitch, setPitch, eavesOffset, setEavesOffset, hipAndGableWidth, setHipAndGableWidth, pitchText }) {
const { getMessage } = useMessage()
@ -10,7 +10,7 @@ export default function HipAndGable({ pitch, setPitch, eavesOffset, setEavesOffs
{getMessage('slope')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={pitch} onChange={(e) => onlyNumberWithDotInputChange(e, setPitch)} />
<input type="text" className="input-origin block" value={pitch} onChange={(e) => setPitch(normalizeDecimalLimit(e.target.value, 2))} />
</div>
<span className="thin">{pitchText}</span>
</div>
@ -19,7 +19,7 @@ export default function HipAndGable({ pitch, setPitch, eavesOffset, setEavesOffs
{getMessage('eaves.offset')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={eavesOffset} onChange={(e) => onlyNumberInputChange(e, setEavesOffset)} />
<input type="text" className="input-origin block" value={eavesOffset} onChange={(e) => setEavesOffset(normalizeDigits(e.target.value))} />
</div>
<span className="thin">mm</span>
</div>
@ -32,7 +32,7 @@ export default function HipAndGable({ pitch, setPitch, eavesOffset, setEavesOffs
type="text"
className="input-origin block"
value={hipAndGableWidth}
onChange={(e) => onlyNumberInputChange(e, setHipAndGableWidth)}
onChange={(e) => setHipAndGableWidth(normalizeDigits(e.target.value))}
/>
</div>
<span className="thin">mm</span>

View File

@ -1,5 +1,5 @@
import { useMessage } from '@/hooks/useMessage'
import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/input-utils'
import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils'
export default function Jerkinhead({
gableOffset,
@ -18,7 +18,7 @@ export default function Jerkinhead({
{getMessage('gable.offset')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={gableOffset} onChange={(e) => onlyNumberInputChange(e, setGableOffset)} />
<input type="text" className="input-origin block" value={gableOffset} onChange={(e) => setGableOffset(normalizeDigits(e.target.value))} />
</div>
<span className="thin">mm</span>
</div>
@ -27,7 +27,7 @@ export default function Jerkinhead({
{getMessage('jerkinhead.width')}
</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={jerkinHeadWidth} onChange={(e) => onlyNumberInputChange(e, setJerkinHeadWidth)} />
<input type="text" className="input-origin block" value={jerkinHeadWidth} onChange={(e) => setJerkinHeadWidth(normalizeDigits(e.target.value))} />
</div>
<span className="thin">mm</span>
</div>
@ -40,7 +40,7 @@ export default function Jerkinhead({
type="text"
className="input-origin block"
value={jerkinHeadPitch}
onChange={(e) => onlyNumberWithDotInputChange(e, setJerkinHeadPitch)}
onChange={(e) => setJerkinHeadPitch(normalizeDecimalLimit(e.target.value, 2))}
/>
</div>
<span className="thin">{pitchText}</span>

View File

@ -1,5 +1,5 @@
import { useMessage } from '@/hooks/useMessage'
import { onlyNumberInputChange, onlyNumberWithDotInputChange } from '@/util/input-utils'
import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils'
export default function Shed({ shedWidth, setShedWidth, shedPitch, setShedPitch, pitchText }) {
const { getMessage } = useMessage()
@ -8,14 +8,14 @@ export default function Shed({ shedWidth, setShedWidth, shedPitch, setShedPitch,
<div className="outline-form mb10">
<span className="mr10">{getMessage('slope')}</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={shedPitch} onChange={(e) => onlyNumberWithDotInputChange(e, setShedPitch)} />
<input type="text" className="input-origin block" value={shedPitch} onChange={(e) => setShedPitch(normalizeDecimalLimit(e.target.value, 2))} />
</div>
<span className="thin">{pitchText}</span>
</div>
<div className="outline-form">
<span className="mr10">{getMessage('shed.width')}</span>
<div className="input-grid mr5" style={{ width: '100px' }}>
<input type="text" className="input-origin block" value={shedWidth} onChange={(e) => onlyNumberInputChange(e, setShedWidth)} />
<input type="text" className="input-origin block" value={shedWidth} onChange={(e) => setShedWidth(normalizeDigits(e.target.value))} />
</div>
<span className="thin">mm</span>
</div>

View File

@ -1,6 +1,6 @@
import { useState } from 'react'
import { useMessage } from '@/hooks/useMessage'
import { onlyNumberInputChange } from '@/util/input-utils'
import { normalizeDigits } from '@/util/input-utils'
export default function Wall({ sleeveOffset, setSleeveOffset, hasSleeve, setHasSleeve }) {
const { getMessage } = useMessage()
@ -29,7 +29,7 @@ export default function Wall({ sleeveOffset, setSleeveOffset, hasSleeve, setHasS
type="text"
className="input-origin block"
value={sleeveOffset}
onChange={(e) => onlyNumberInputChange(e, setSleeveOffset)}
onChange={(e) => setSleeveOffset(normalizeDigits(e.target.value))}
readOnly={hasSleeve === '0'}
/>
</div>

View File

@ -5,7 +5,7 @@ import { usePopup } from '@/hooks/usePopup'
import WithDraggable from '@/components/common/draggable/WithDraggable'
import { canvasState } from '@/store/canvasAtom'
import { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
import { onlyNumberInputChange } from '@/util/input-utils'
import { normalizeDigits } from '@/util/input-utils'
export default function PlanSizeSetting(props) {
const { setIsShow, horizon, vertical, id, pos = { x: 985, y: 180 }, settingsData, setSettingsData, settingsDataSave, setSettingsDataSave } = props
@ -24,15 +24,15 @@ export default function PlanSizeSetting(props) {
setPlanSizeSettingMode((prev) => {
return {
...prev,
originHorizon: Number(planSizeSettingMode.originHorizon),
originVertical: Number(planSizeSettingMode.originVertical),
originHorizon: Number(normalizeDigits(planSizeSettingMode.originHorizon)),
originVertical: Number(normalizeDigits(planSizeSettingMode.originVertical)),
}
})
setSettingsData({
...settingsData,
originHorizon: Number(planSizeSettingMode.originHorizon),
originVertical: Number(planSizeSettingMode.originVertical),
originHorizon: Number(normalizeDigits(planSizeSettingMode.originHorizon)),
originVertical: Number(normalizeDigits(planSizeSettingMode.originVertical)),
})
canvas.setWidth(planSizeSettingMode.originHorizon)
@ -46,14 +46,15 @@ export default function PlanSizeSetting(props) {
const changeInput = (value, e) => {
const { name } = e.target
if (Number(value) > 100000) {
value = 100000
let n = Number(normalizeDigits(value))
if (n > 100000) {
n = 100000
}
setPlanSizeSettingMode((prev) => {
return {
...prev,
[name]: Number(value) / 10,
[name]: n / 10,
}
})
}
@ -77,7 +78,7 @@ export default function PlanSizeSetting(props) {
className="input-origin block"
name={`originHorizon`}
value={planSizeSettingMode.originHorizon * 10}
onChange={(e) => onlyNumberInputChange(e, changeInput)}
onChange={(e) => changeInput(normalizeDigits(e.target.value), e)}
/>
</div>
<span className="thin">mm</span>
@ -90,7 +91,7 @@ export default function PlanSizeSetting(props) {
className="input-origin block"
name={`originVertical`}
value={planSizeSettingMode.originVertical * 10}
onChange={(e) => onlyNumberInputChange(e, changeInput)}
onChange={(e) => changeInput(normalizeDigits(e.target.value), e)}
/>
</div>
<span className="thin">mm</span>

View File

@ -25,6 +25,7 @@ import { isObjectNotEmpty } from '@/util/common-utils'
import { roofMaterialsAtom } from '@/store/settingAtom'
import { useMasterController } from '@/hooks/common/useMasterController'
import { ROOF_MATERIAL_LAYOUT } from '@/components/floor-plan/modal/placementShape/PlacementShapeSetting'
import { useSwal } from '@/hooks/useSwal'
export const ToggleonMouse = (e, act, target) => {
const listWrap = e.target.closest(target)
@ -75,6 +76,8 @@ export default function Header(props) {
const [commonCode, setCommonCode] = useRecoilState(commonCodeState)
const { promiseGet } = useAxios()
const { swalFire } = useSwal()
/**
* 지붕재 목록 Header에서 조회
*/
@ -130,17 +133,21 @@ export default function Header(props) {
{ id: 1, name: 'HANASYS ORDER', link: `${qOrderUrl}?autoLoginParam1=${encodeURIComponent(res.data)}`, target: '_blank' },
{ id: 2, name: 'HANASYS Musubi', link: `${qMusubiUrl}?autoLoginParam1=${encodeURIComponent(res.data)}`, target: '_blank' },
{ id: 3, name: getMessage('site.header.link2'), link: `https://q-warranty.q-cells.jp/seller_login`, target: '_blank' },
{ id: 4, name: 'Q.PARTNERS', link: `https://q-partners.q-cells.jp/qcast_login.php`, target: '_blank' },
]
: userSession.groupId === '60000'
? [
{ id: 0, name: getMessage('site.header.link1'), target: '_blank' },
{ id: 1, name: 'HANASYS ORDER', link: `${qOrderUrl}?autoLoginParam1=${encodeURIComponent(res.data)}`, target: '_blank' },
{ id: 2, name: getMessage('site.header.link2'), link: `https://q-warranty.q-cells.jp/seller_login`, target: '_blank' },
{ id: 3, name: 'Q.PARTNERS', link: `https://q-partners.q-cells.jp/qcast_login.php`, target: '_blank' },
]
: [
{ id: 0, name: getMessage('site.header.link1'), target: '_blank' },
{ id: 1, name: 'HANASYS Musubi', link: `${qMusubiUrl}?autoLoginParam1=${encodeURIComponent(res.data)}`, target: '_blank' },
{ id: 2, name: getMessage('site.header.link2'), link: `https://q-warranty.q-cells.jp/seller_login`, target: '_blank' },
{ id: 3, name: 'Q.PARTNERS', link: `https://q-partners.q-cells.jp/qcast_login.php`, target: '_blank' },
],
)
onChangeSelect({ id: 0, name: getMessage('site.header.link1') })
@ -232,11 +239,32 @@ export default function Header(props) {
key={`${menu.id}`}
href={menu.url}
replace={true}
onClick={() => {
// moveHome()
removeStuffRecoil(menu)
if (pathName === '/' && menu.id !== 8) {
window.location.reload()
onClick={(e) => {
if(pathName === '/floor-plan') {
e.preventDefault() //
e.stopPropagation() //
swalFire({
text : getMessage('common.link.confirm'), //
type : 'confirm',
confirmFn: () => {
//
removeStuffRecoil(menu)
if (pathName === '/' && menu.id !== 8) {
window.location.reload()
} else {
router.push(menu.url)
}
router.push(menu.url)
}
})
} else {
removeStuffRecoil(menu)
if (pathName === '/' && menu.id !== 8) {
window.location.reload()
} else {
router.push(menu.url)
}
}
}}
>
@ -258,9 +286,24 @@ export default function Header(props) {
scroll={false}
href={m.url}
replace={true}
onClick={() => {
removeStuffRecoil(m)
}}
onClick={(e) => {
if(pathName === '/floor-plan') {
e.preventDefault() //
e.stopPropagation() //
swalFire({
text: getMessage('common.link.confirm'), //
type: 'confirm',
confirmFn: () => {
//
removeStuffRecoil(m)
router.push(m.url)
}
})
} else {
removeStuffRecoil(m)
}
}
}
>
{getMessage(m.name)}
</Link>
@ -284,14 +327,28 @@ export default function Header(props) {
<h1 className="logo">
<Link
href={'/'}
onClick={() => {
onClick={(e) => {
setStuffSearch({
...stuffSearch,
code: 'DELETE',
})
if (pathName === '/') {
window.location.reload()
} else if(pathName === '/floor-plan') {
e.preventDefault() //
e.stopPropagation() //
swalFire({
text: getMessage('common.link.confirm'), //
type: 'confirm',
confirmFn: () => {
//
//removeStuffRecoil(m)
router.push('/')
}
})
}
}}
></Link>
</h1>

View File

@ -22,6 +22,7 @@ import { stuffSearchState } from '@/store/stuffAtom'
import { QcastContext } from '@/app/QcastProvider'
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
import { useSwal } from '@/hooks/useSwal'
import { sanitizeIntegerInputEvent } from '@/util/input-utils'
export default function StuffDetail() {
const [stuffSearch, setStuffSearch] = useRecoilState(stuffSearchState)
@ -1239,10 +1240,10 @@ export default function StuffDetail() {
}
//
if (!formData.address) {
fieldNm = getMessage('stuff.detail.address')
errors = fieldNm
}
// if (!formData.address) {
// fieldNm = getMessage('stuff.detail.address')
// errors = fieldNm
// }
//
if (!formData.prefId || formData.prefId === '0') {
@ -1634,13 +1635,15 @@ export default function StuffDetail() {
//
const handleKeyUp = (e) => {
let input = e.target
input.value = input.value.replace(/[^0-9]/g, '')
// let input = e.target
// input.value = input.value.replace(/[^0-9]/g, '')
sanitizeIntegerInputEvent(e)
}
const handleBlur = (e) => {
let input = e.target
input.value = input.value.replace(/[^0-9]/g, '')
// let input = e.target
// input.value = input.value.replace(/[^0-9]/g, '')
sanitizeIntegerInputEvent(e)
}
//

View File

@ -9,6 +9,7 @@ import { useMessage } from '@/hooks/useMessage'
import { isNotEmptyArray } from '@/util/common-utils'
import { useSwal } from '@/hooks/useSwal'
import { QcastContext } from '@/app/QcastProvider'
import { sanitizeIntegerInputEvent } from '@/util/input-utils'
export default function FindAddressPop(props) {
const globalLocaleState = useRecoilValue(globalLocaleStore)
@ -113,12 +114,12 @@ export default function FindAddressPop(props) {
//
const handleKeyUp = (e) => {
let input = e.target
input.value = input.value.replace(/[^0-9]/g, '')
if (e.key === 'Enter') {
searchPostNum()
}
// let input = e.target
// input.value = input.value.replace(/[^0-9]/g, '')
// if (e.key === 'Enter') {
// searchPostNum()
// }
sanitizeIntegerInputEvent(e)
}
//

View File

@ -11,6 +11,8 @@ import { isObjectNotEmpty, queryStringFormatter } from '@/util/common-utils'
import QPagination from '@/components/common/pagination/QPagination'
import { useSwal } from '@/hooks/useSwal'
import { QcastContext } from '@/app/QcastProvider'
import { sanitizeDecimalInputEvent } from '@/util/input-utils'
export default function PlanRequestPop(props) {
const [pageNo, setPageNo] = useState(1) //
const [pageSize, setPageSize] = useState(20) //
@ -233,16 +235,16 @@ export default function PlanRequestPop(props) {
//
const handleKeyUp = (e) => {
let input = e.target
input.value = input.value.replace(/[^0-9]/g, '')
sanitizeIntegerInputEvent(e)
if (e.key === 'Enter') {
onSubmit(pageNo, 'S')
}
}
const handleBlur = (e) => {
let input = e.target
input.value = input.value.replace(/[^0-9]/g, '')
// let input = e.target
// input.value = input.value.replace(/[^0-9]/g, '')
sanitizeIntegerInputEvent(e)
}
//

View File

@ -18,7 +18,7 @@ import { usePlan } from '@/hooks/usePlan'
import { usePopup } from '@/hooks/usePopup'
import { QcastContext } from '@/app/QcastProvider'
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
export default function Simulator() {
// global
const { setIsGlobalLoading } = useContext(QcastContext)
@ -34,6 +34,7 @@ export default function Simulator() {
const { get } = useAxios()
const { getMessage } = useMessage()
const { setSelectedMenu } = useCanvasMenu()
//
const [chartData, setChartData] = useState([])
@ -48,34 +49,25 @@ export default function Simulator() {
return isNaN(num) ? 0 : num
}),
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)',
'rgba(0, 99, 132, 0.2)',
'rgba(0, 162, 235, 0.2)',
'rgba(0, 206, 86, 0.2)',
'rgba(0, 192, 192, 0.2)',
'rgba(0, 102, 255, 0.2)',
'rgba(0, 159, 64, 0.2)',
],
borderColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)',
'rgba(0, 99, 132, 0.2)',
'rgba(0, 162, 235, 0.2)',
'rgba(0, 206, 86, 0.2)',
'rgba(0, 192, 192, 0.2)',
'rgba(0, 102, 255, 0.2)',
'rgba(0, 159, 64, 0.2)',
],
backgroundColor: (context) => {
const chart = context.chart
const { ctx, chartArea } = chart
if (!chartArea) {
// This case happens on initial chart load
return null
}
const gradient = ctx.createLinearGradient(0, chartArea.bottom, 0, chartArea.top)
gradient.addColorStop(0, '#4FC3F7') // Light blue at bottom
gradient.addColorStop(0.3, '#2FA8E0') // Original blue
gradient.addColorStop(0.7, '#1976D2') // Medium blue
gradient.addColorStop(1, '#0D47A1') // Dark blue at top
return gradient
},
borderColor: '#2FA8E0' ,
borderWidth: 1,
},
],
@ -103,6 +95,7 @@ export default function Simulator() {
}
useEffect(() => {
setSelectedMenu('simulation')
/* 초기화 작업 */
setChartData([])
setObjectDetail({})
@ -193,10 +186,10 @@ export default function Simulator() {
setChartData(hatsudenryouAll)
break
case 'B':
setChartData(hatsudenryouAllSnow)
setChartData(hatsudenryouPeakcutAll)
break
case 'C':
setChartData(hatsudenryouPeakcutAll)
setChartData(hatsudenryouAllSnow)
break
case 'D':
setChartData(hatsudenryouPeakcutAllSnow)

View File

@ -1,7 +1,7 @@
import getConfigs from './config.common'
// 환경마다 달라져야 할 변수, 값들을 정의합니다. (여기는 development 환경에 맞는 값을 지정합니다.)
const baseUrl = 'https://dev.hanssys.jp'
const baseUrl = 'https://dev.hanasys.jp'
const mode = 'development'
// 환경마다 달라져야 할 값들을 getConfig 함수에 전달합니다.

View File

@ -101,6 +101,7 @@ export function useCanvasPopupStatusController(param = 1) {
popupType: popupType.toString(),
// popupStatus: popupType === 1 ? arg : JSON.stringify(arg).replace(/"/g, '\"'),
popupStatus: JSON.stringify(arg).replace(/"/g, '\"'),
//hajebichi: arg.roofConstructions?.[0]?.addRoof?.hajebichi || '',
}
postFetcher(`/api/v1/canvas-popup-status`, params)
},

View File

@ -1,17 +1,18 @@
import { useEffect } from 'react'
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
import { useRecoilState, useRecoilValue } from 'recoil'
import { wordDisplaySelector } from '@/store/settingAtom'
import { useEvent } from '@/hooks/useEvent'
import { checkLineOrientation, getDistance } from '@/util/canvas-util'
import { commonUtilsState, dimensionLineSettingsState } from '@/store/commonUtilsAtom'
import { fontSelector } from '@/store/fontAtom'
import { canvasState, currentMenuState } from '@/store/canvasAtom'
import { canvasState } from '@/store/canvasAtom'
import { v4 as uuidv4 } from 'uuid'
import { usePopup } from '@/hooks/usePopup'
import Distance from '@/components/floor-plan/modal/distance/Distance'
import { usePolygon } from '@/hooks/usePolygon'
import { useObjectBatch } from '@/hooks/object/useObjectBatch'
import { BATCH_TYPE } from '@/common/common'
import { useMouse } from '@/hooks/useMouse'
export function useCommonUtils() {
const canvas = useRecoilValue(canvasState)
@ -25,6 +26,7 @@ export function useCommonUtils() {
const { addPopup, closeAll, targetClose } = usePopup()
const { drawDirectionArrow, addLengthText } = usePolygon()
const { applyDormers } = useObjectBatch({})
const { getIntersectMousePoint } = useMouse()
useEffect(() => {
commonTextMode()
@ -213,7 +215,7 @@ export function useCommonUtils() {
addCanvasMouseEventListener('mouse:down', (e) => {
let groupObjects = []
const pointer = canvas.getPointer(e.e)
const pointer = getIntersectMousePoint(e)
let point
@ -654,7 +656,11 @@ export function useCommonUtils() {
clonedObj.setCoords()
clonedObj.fire('modified')
clonedObj.fire('polygonMoved')
clonedObj.set({ direction: obj.direction, directionText: obj.directionText, roofMaterial: obj.roofMaterial })
clonedObj.set({
direction: obj.direction,
directionText: obj.directionText,
roofMaterial: obj.roofMaterial,
})
obj.lines.forEach((line, index) => {
clonedObj.lines[index].set({ attributes: line.attributes })

View File

@ -1,15 +1,18 @@
import { useRecoilValue } from 'recoil'
import { canvasState, dotLineGridSettingState } from '@/store/canvasAtom'
import { canvasState, canvasZoomState, dotLineGridSettingState } from '@/store/canvasAtom'
import { useEffect } from 'react'
import { gridColorState } from '@/store/gridAtom'
import { gridDisplaySelector } from '@/store/settingAtom'
const GRID_PADDING = 5
export function useGrid() {
const canvas = useRecoilValue(canvasState)
const dotLineGridSetting = useRecoilValue(dotLineGridSettingState)
const gridColor = useRecoilValue(gridColorState)
const isGridDisplay = useRecoilValue(gridDisplaySelector)
const zoom = useRecoilValue(canvasZoomState)
useEffect(() => {
if (!canvas) {
@ -90,14 +93,32 @@ export function useGrid() {
}
if (patternData.lineGridDisplay) {
for (let i = 0; i < 5000 / patternData.gridVertical + 1; i++) {
// 캔버스의 실제 보이는 영역 계산
const canvasWidth = canvas.getWidth()
const canvasHeight = canvas.getHeight()
const currentZoom = canvas.getZoom()
const viewportTransform = canvas.viewportTransform
const visibleLeft = -viewportTransform[4] / currentZoom
const visibleTop = -viewportTransform[5] / currentZoom
const visibleRight = visibleLeft + canvasWidth / currentZoom
const visibleBottom = visibleTop + canvasHeight / currentZoom
// 여유 공간 추가
const padding = 200
const gridLeft = visibleLeft - padding
const gridTop = visibleTop - padding
const gridRight = visibleRight + padding
const gridBottom = visibleBottom + padding
// 가로선 생성 (수평선)
const horizontalGridRange = gridBottom - gridTop
const horizontalGridCount = Math.ceil(horizontalGridRange / patternData.gridVertical) + 2
for (let i = 0; i < horizontalGridCount; i++) {
const y = gridTop + i * patternData.gridVertical
const horizontalLine = new fabric.Line(
[
-1500,
-1500 + i * patternData.gridVertical - patternData.gridVertical / 2,
3000,
-1500 + i * patternData.gridVertical - patternData.gridVertical / 2,
],
[gridLeft, y, gridRight, y],
{
stroke: gridColor,
strokeWidth: 1,
@ -118,14 +139,14 @@ export function useGrid() {
canvas.add(horizontalLine)
}
for (let i = 0; i < 5000 / patternData.gridHorizon + 1; i++) {
// 세로선 생성 (수직선)
const verticalGridRange = gridRight - gridLeft
const verticalGridCount = Math.ceil(verticalGridRange / patternData.gridHorizon) + 2
for (let i = 0; i < verticalGridCount; i++) {
const x = gridLeft + i * patternData.gridHorizon
const verticalLine = new fabric.Line(
[
-1500 + i * patternData.gridHorizon - patternData.gridHorizon / 2,
-1500,
-1500 + i * patternData.gridHorizon - patternData.gridHorizon / 2,
3000,
],
[x, gridTop, x, gridBottom],
{
stroke: gridColor,
strokeWidth: 1,
@ -148,7 +169,7 @@ export function useGrid() {
}
canvas.renderAll()
}, [dotLineGridSetting])
}, [dotLineGridSetting, zoom])
const move = (object, x, y) => {
object.set({

View File

@ -6,7 +6,6 @@ import EavesGableEdit from '@/components/floor-plan/modal/eavesGable/EavesGableE
import MovementSetting from '@/components/floor-plan/modal/movement/MovementSetting'
import WallLineOffsetSetting from '@/components/floor-plan/modal/wallLineOffset/WallLineOffsetSetting'
import RoofAllocationSetting from '@/components/floor-plan/modal/roofAllocation/RoofAllocationSetting'
import Slope from '@/components/floor-plan/modal/Slope'
import PlacementShapeDrawing from '@/components/floor-plan/modal/placementShape/PlacementShapeDrawing'
import PlacementSurfaceSetting from '@/components/floor-plan/modal/placementSurface/PlacementSurfaceSetting'
import ObjectSetting from '@/components/floor-plan/modal/object/ObjectSetting'
@ -20,9 +19,8 @@ import { useRecoilState, useRecoilValue } from 'recoil'
import { canvasState, currentMenuState } from '@/store/canvasAtom'
import { MENU } from '@/common/common'
import { useTrestle } from '@/hooks/module/useTrestle'
import { usePolygon } from '@/hooks/usePolygon'
import { useOrientation } from '@/hooks/module/useOrientation'
import { corridorDimensionSelector, settingModalFirstOptionsState } from '@/store/settingAtom'
import { corridorDimensionSelector } from '@/store/settingAtom'
/**
* 메뉴 처리
@ -33,12 +31,13 @@ export default function useMenu() {
const currentMenu = useRecoilValue(currentMenuState)
const canvas = useRecoilValue(canvasState)
const [popupId, setPopupId] = useState(uuidv4())
const { addPopup } = usePopup()
const { addPopup, closeAll } = usePopup()
const { deleteAllSurfacesAndObjects } = useSurfaceShapeBatch({})
const { clear: trestleClear, setAllModuleSurfaceIsComplete } = useTrestle()
const { nextStep } = useOrientation()
const [corridorDimension, setCorridorDimension] = useRecoilState(corridorDimensionSelector)
const handleMenu = (type) => {
closeAll()
if (type === 'outline') {
// 지붕 덮개 메뉴의 경우는 복도치수로 적용한다.
setCorridorDimension(0)
@ -67,6 +66,9 @@ export default function useMenu() {
case MENU.ROOF_COVERING.ROOF_SHAPE_ALLOC:
addPopup(popupId, 1, <RoofAllocationSetting id={popupId} />)
break
case MENU.ROOF_COVERING.ALL_REMOVE:
deleteAllSurfacesAndObjects()
break
}
}

View File

@ -9,6 +9,8 @@ import { deleteBackGroundImage, setBackGroundImage } from '@/lib/imageActions'
import { settingModalFirstOptionsState } from '@/store/settingAtom'
import { popSpinnerState } from '@/store/popupAtom'
import Config from '@/config/config.export'
import { useMessage } from '@/hooks/useMessage'
import { logger } from '@/util/logger'
/**
* 배경 이미지 관리
@ -23,7 +25,9 @@ import Config from '@/config/config.export'
* @returns {object}
*/
export function useRefFiles() {
const converterUrl = process.env.NEXT_PUBLIC_CONVERTER_API_URL
const converterDwgUrl = process.env.NEXT_PUBLIC_CONVERTER_DWG_API_URL
const converterDxfUrl = process.env.NEXT_PUBLIC_CONVERTER_DXF_API_URL
const { getMessage } = useMessage()
const [refImage, setRefImage] = useState(null)
const [refFileMethod, setRefFileMethod] = useState('1')
const [mapPositionAddress, setMapPositionAddress] = useState('')
@ -50,12 +54,12 @@ export function useRefFiles() {
* @param {*} file
*/
const handleRefFile = (file) => {
console.log('handleRefFile', file)
console.log('refImage', refImage)
logger.log('handleRefFile', file)
logger.log('refImage', refImage)
if (refImage) {
swalFire({
text: '파일을 변경하시겠습니까?',
text: getMessage('common.message.ref.file.change'),
type: 'confirm',
confirmFn: () => {
refFileSetting(file)
@ -71,15 +75,29 @@ export function useRefFiles() {
* @param {File} file
*/
const refFileSetting = (file) => {
console.log('🚀 ~ refFileSetting ~ file:', file)
logger.log('🚀 ~ refFileSetting ~ file:', file)
setPopSpinnerStore(true)
const newOption1 = settingModalFirstOptions.option1.map((option) => ({
...option,
selected: option.column === 'imageDisplay' ? true : option.selected,
}))
setSettingModalFirstOptions((prev) => ({
...prev,
option1: newOption1,
}))
if (file.name.split('.').pop() === 'dwg') {
handleUploadConvertRefFile(file)
handleUploadConvertRefFile(file, converterDwgUrl);
} else if (file.name.split('.').pop() === 'dxf') {
handleUploadConvertRefFile(file, converterDxfUrl);
} else {
if (file && ['image/png', 'image/jpg', 'image/jpeg', 'image/bmp', 'image/gif'].includes(file.type)) {
handleUploadImageRefFile(file)
} else {
swalFire({
text: '이미지가 아닙니다.',
text: getMessage('common.message.ref.file.noImage'),
type: 'alert',
icon: 'error',
})
@ -92,12 +110,12 @@ export function useRefFiles() {
*/
const handleFileDelete = async () => {
swalFire({
text: '삭제하시겠습니까?',
text: getMessage('common.message.data.delete'),
type: 'confirm',
confirmFn: async () => {
setPopSpinnerStore(true)
console.log('🚀 ~ handleFileDelete ~ handleFileDelete:', refImage)
console.log('🚀 ~ handleFileDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName)
logger.log('🚀 ~ handleFileDelete ~ handleFileDelete:', refImage)
logger.log('🚀 ~ handleFileDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName)
await del({ url: `${Config().baseUrl}/api/image/upload?fileName=${currentCanvasPlan.bgImageName}` })
setCurrentBgImage(null)
await deleteBackGroundImage({
@ -114,11 +132,11 @@ export function useRefFiles() {
*/
const handleAddressDelete = async () => {
swalFire({
text: '삭제하시겠습니까?',
text: getMessage('common.message.data.delete'),
type: 'confirm',
confirmFn: async () => {
console.log('🚀 ~ handleAddressDelete ~ handleAddressDelete:', refImage)
console.log('🚀 ~ handleAddressDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName)
logger.log('🚀 ~ handleAddressDelete ~ handleAddressDelete:', refImage)
logger.log('🚀 ~ handleAddressDelete ~ currentCanvasPlan.bgImageName:', currentCanvasPlan.bgImageName)
await del({ url: `${Config().baseUrl}/api/image/map?fileName=${currentCanvasPlan.bgImageName}` })
setMapPositionAddress('')
setCurrentBgImage(null)
@ -134,7 +152,7 @@ export function useRefFiles() {
* 주소로 구글 이미지 다운로드하여 캔버스 배경으로 로드
*/
const handleMapImageDown = async () => {
console.log('🚀 ~ handleMapImageDown ~ handleMapImageDown:')
debugger; logger.log('🚀 ~ handleMapImageDown ~ handleMapImageDown:')
if (queryRef.current.value === '' || queryRef.current.value === null) {
return
}
@ -152,7 +170,7 @@ export function useRefFiles() {
const res = await get({
url: `${Config().baseUrl}/api/image/map?q=${queryRef.current.value}&fileNm=${currentCanvasPlan.id}&zoom=20`,
})
console.log('🚀 ~ handleMapImageDown ~ res:', res)
logger.log('🚀 ~ handleMapImageDown ~ res:', res)
setCurrentBgImage(`${process.env.NEXT_PUBLIC_AWS_S3_BASE_URL}/${res.fileName}`)
await setBackGroundImage({
@ -170,9 +188,11 @@ export function useRefFiles() {
// if (!currentBgImage) {
// return
// }
console.log('🚀 ~ useEffect ~ currentBgImage:', currentBgImage)
logger.log('🚀 ~ useEffect ~ currentBgImage:', currentBgImage)
if (currentBgImage) {
handleBackImageLoadToCanvas(currentBgImage)
handleBackImageLoadToCanvas(currentBgImage, () => {
logger.log('Background image loaded successfully')
})
setCurrentCanvasPlan((prev) => ({
...prev,
// bgImageName: refImage?.name ?? null,
@ -194,16 +214,16 @@ export function useRefFiles() {
* @param {*} file
*/
const handleUploadImageRefFile = async (file) => {
setPopSpinnerStore(true)
const newOption1 = settingModalFirstOptions.option1.map((option) => ({
...option,
selected: option.column === 'imageDisplay' ? true : option.selected,
}))
setSettingModalFirstOptions((prev) => ({
...prev,
option1: newOption1,
}))
// setPopSpinnerStore(true)
// const newOption1 = settingModalFirstOptions.option1.map((option) => ({
// ...option,
// selected: option.column === 'imageDisplay' ? true : option.selected,
// }))
//
// setSettingModalFirstOptions((prev) => ({
// ...prev,
// option1: newOption1,
// }))
const formData = new FormData()
formData.append('file', file)
@ -212,7 +232,7 @@ export function useRefFiles() {
url: `${Config().baseUrl}/api/image/upload`,
data: formData,
})
console.log('🚀 ~ handleUploadImageRefFile ~ res:', res)
logger.log('🚀 ~ handleUploadImageRefFile ~ res:', res)
setCurrentBgImage(`${process.env.NEXT_PUBLIC_AWS_S3_BASE_URL}/${res.fileName}`)
setRefImage(file)
@ -222,7 +242,7 @@ export function useRefFiles() {
// imagePath: `${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`,
imagePath: `${res.filePath}`,
}
console.log('🚀 ~ handleUploadImageRefFile ~ params:', params)
logger.log('🚀 ~ handleUploadImageRefFile ~ params:', params)
await setBackGroundImage(params)
setPopSpinnerStore(false)
}
@ -230,14 +250,15 @@ export function useRefFiles() {
/**
* RefFile이 캐드 도면 파일일 경우 변환하여 이미지로 저장
* @param {*} file
* @param converterUrl
*/
const handleUploadConvertRefFile = async (file) => {
const handleUploadConvertRefFile = async (file, converterUrl) => {
const formData = new FormData()
formData.append('file', file)
/** 캐드 도면 파일 변환 */
const res = await post({ url: converterUrl, data: formData })
console.log('🚀 ~ handleUploadConvertRefFile ~ res:', res)
logger.log('🚀 ~ handleUploadConvertRefFile ~ res:', res)
// Convert Base64 to Blob
const base64Data = res.Files[0].FileData
@ -269,7 +290,7 @@ export function useRefFiles() {
url: `${Config().baseUrl}/api/image/cad`,
data: newFormData,
})
console.log('🚀 ~ handleUploadConvertRefFile ~ result:', result)
logger.log('🚀 ~ handleUploadConvertRefFile ~ result:', result)
setCurrentBgImage(`${process.env.NEXT_PUBLIC_AWS_S3_BASE_URL}/${result.fileName}`)
// setCurrentBgImage(result.filePath)
@ -281,8 +302,9 @@ export function useRefFiles() {
// imagePath: `${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`,
imagePath: `${result.filePath}`,
}
console.log('🚀 ~ handleUploadImageRefFile ~ params:', params)
logger.log('🚀 ~ handleUploadImageRefFile ~ params:', params)
await setBackGroundImage(params)
setPopSpinnerStore(false)
}
/**

View File

@ -6,6 +6,8 @@ import { POLYGON_TYPE } from '@/common/common'
import { useEvent } from '@/hooks/useEvent'
import { useLine } from '@/hooks/useLine'
import { outerLinePointsState } from '@/store/outerLineAtom'
import { usePolygon } from '@/hooks/usePolygon'
import { useText } from '@/hooks/useText'
const ROOF_COLOR = {
0: 'rgb(199,240,213)',
@ -13,6 +15,7 @@ const ROOF_COLOR = {
2: 'rgb(187,204,255)',
3: 'rgb(228,202,255)',
}
export function useRoofFn() {
const canvas = useRecoilValue(canvasState)
const selectedRoofMaterial = useRecoilValue(selectedRoofMaterialSelector)
@ -20,6 +23,8 @@ export function useRoofFn() {
const { addCanvasMouseEventListener, initEvent } = useEvent()
const resetPoints = useResetRecoilState(outerLinePointsState)
const { addPitchText } = useLine()
const { setPolygonLinesActualSize } = usePolygon()
const { changeCorridorDimensionText } = useText()
//면형상 선택 클릭시 지붕 패턴 입히기
function setSurfaceShapePattern(polygon, mode = 'onlyBorder', trestleMode = false, roofMaterial, isForceChange = false, isDisplay = false) {
@ -27,6 +32,9 @@ export function useRoofFn() {
if (!polygon) {
return
}
if (polygon.wall) {
return
}
if (polygon.points.length < 3) {
return
}
@ -44,6 +52,7 @@ export function useRoofFn() {
let width = (roofMaterial.width || 226) / 10
let height = (roofMaterial.length || 158) / 10
const index = roofMaterial.index ?? 0
let roofStyle = 2
const inputPatternSize = { width: width, height: height } //임시 사이즈
@ -169,6 +178,8 @@ export function useRoofFn() {
polygon.set('fill', null)
polygon.set('fill', pattern)
polygon.roofMaterial = roofMaterial
setPolygonLinesActualSize(polygon)
changeCorridorDimensionText()
polygon.canvas?.renderAll()
} catch (e) {
console.log(e)
@ -202,7 +213,7 @@ export function useRoofFn() {
}
const roof = roofBase[0]
const wall = roof.wall
const wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes?.roofId === roof.id)
const checkPolygon = new fabric.Polygon(roof.points, {
name: 'moveRoofPolygon',
@ -230,16 +241,11 @@ export function useRoofFn() {
const texts = canvas.getObjects().filter((obj) => obj.type === 'text' && (obj.attributes?.roofId === roof.id || obj.parentId === roof.id))
texts.forEach((text) => canvas.remove(text))
const outerLines = canvas.getObjects().filter((obj) => obj.type === 'QLine' && (obj.attributes?.roofId === roof.id || obj.parentId === roof.id))
const allRoofObject = canvas
.getObjects()
.filter(
(obj) => obj !== roof && /*obj !== wall &&*/ (obj.attributes?.roofId === roof.id || obj.parentId === roof.id || obj.parentId === wall.id),
)
// // Calculate the movement delta
.filter((obj) => obj !== roof && (obj.attributes?.roofId === roof.id || obj.parentId === roof.id || obj.parentId === wall.id))
/** 지붕이 움직인 만큼의 delta를 구한다. */
const originalRoofLeft = roof.left
const originalRoofTop = roof.top
@ -308,7 +314,15 @@ export function useRoofFn() {
}
function convertAbsolutePoint(area) {
return area.points.map((p) => fabric.util.transformPoint({ x: p.x - area.pathOffset.x, y: p.y - area.pathOffset.y }, area.calcTransformMatrix()))
return area.points.map((p) =>
fabric.util.transformPoint(
{
x: p.x - area.pathOffset.x,
y: p.y - area.pathOffset.y,
},
area.calcTransformMatrix(),
),
)
}
const removeOuterLines = (currentMousePos) => {

View File

@ -0,0 +1,37 @@
import * as turf from '@turf/turf'
export const useTurf = () => {
/**
* 모듈이 배치면 안에 있는지 확인
* @param {*} module
* @param {*} surface
* @param spare
* @returns
*/
const checkModuleDisjointSurface = (module, surface, spare = 1) => {
// 표면 영역을 spare만큼 수동 확장
const expandedSurface = {
type: 'Polygon',
coordinates: [
surface.geometry.coordinates[0].map(([x, y]) => {
// 각 점을 바깥쪽으로 2 단위씩 이동
const coords = surface.geometry.coordinates[0]
const centerX = coords.slice(0, -1).reduce((sum, [x, y]) => sum + x, 0) / (coords.length - 1)
const centerY = coords.slice(0, -1).reduce((sum, [x, y]) => sum + y, 0) / (coords.length - 1)
return [x < centerX ? x - spare : x + spare, y < centerY ? y - spare : y + spare]
}),
],
}
const isWithin = turf.booleanContains(expandedSurface, module) || turf.booleanWithin(module, expandedSurface)
const isContact = turf.booleanIntersects(module, expandedSurface) && !turf.booleanOverlap(module, expandedSurface)
return isWithin || isContact
}
return {
checkModuleDisjointSurface,
}
}

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