Compare commits
1005 Commits
main
...
feature/sk
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d0ca5d838 | |||
| cb7950cc8c | |||
| a2cd08484d | |||
| f6a5e5db16 | |||
| 58d6dae84a | |||
| 6d20488feb | |||
| 3a3ff7c156 | |||
| d548b0e1f4 | |||
| ddf326ca6b | |||
| 8b2cf6a9d3 | |||
| 41de001986 | |||
| 1385683bce | |||
| 92fd17ed71 | |||
| 320080e0c1 | |||
| d32553416c | |||
| 1aa6bc79a8 | |||
| b480345b24 | |||
| 5f726bf5db | |||
| 9a2c6adb96 | |||
| 75312f5ccf | |||
| ba94dd0579 | |||
| 384c68c1ef | |||
| dfed51a758 | |||
| 15270b3d7d | |||
| 01f160fa8b | |||
| c58c1f2106 | |||
| 0461f129be | |||
|
|
bf0e1e4cb0 | ||
|
|
340c7669af | ||
|
|
8cca1e9937 | ||
| c407709838 | |||
| 56a3853a00 | |||
| e8adf1659f | |||
| 9786c1fbf7 | |||
| 4469691618 | |||
|
|
f8ee6a3887 | ||
|
|
c8421d1253 | ||
|
|
633f417548 | ||
|
|
c850dd3f13 | ||
| c4a894fdd1 | |||
| 95f0428ceb | |||
| e94b703e6c | |||
| 9c0403c947 | |||
| 4df39defc6 | |||
| a1edaf03fb | |||
|
|
3aac4f8ac0 | ||
|
|
6cccdfb987 | ||
|
|
3fd1a0b575 | ||
|
|
1ddc176e7b | ||
|
|
ef910385d3 | ||
| 751b804487 | |||
| 0efc90f135 | |||
|
|
bbd8a43864 | ||
|
|
46710533b5 | ||
| eeab13b9cd | |||
|
|
53d07c5023 | ||
|
|
f5977e87ed | ||
| 0acd9e422f | |||
|
|
c58146ca53 | ||
| 960d31ffdd | |||
| 93e54812c3 | |||
| 816e440ba0 | |||
| a8d9988f24 | |||
| 21943536c9 | |||
|
|
99c7759e00 | ||
| 7d9b6d5225 | |||
|
|
36d16069e0 | ||
| f912a8474e | |||
| d37b191139 | |||
| 7c9fd0f698 | |||
| 650e72d91d | |||
| fb20a1676b | |||
| b651aab959 | |||
| cf969d6f1c | |||
| 0666612c0a | |||
| 3811f224d6 | |||
|
|
fda35b6927 | ||
| a183453390 | |||
|
|
760becfb0d | ||
|
|
098fb8efc6 | ||
|
|
473f4f8d74 | ||
| c358cba0c5 | |||
| ff5c335c9b | |||
| 9999d1bae0 | |||
| c5700c62cc | |||
| 08ae9ac7b9 | |||
| c4baa119b1 | |||
| acffaa5ebd | |||
| 87d058fd07 | |||
| 61a44704fd | |||
| ee26f28446 | |||
| 71a7fab4ea | |||
| 114bf94d92 | |||
| 6dd66ee27d | |||
| 662b38aac7 | |||
| 8f2a78ef1e | |||
| 872c27734b | |||
| 098147bc6b | |||
| cee1567438 | |||
| f15ff10bf6 | |||
| 38a1907842 | |||
| 0c8374b43d | |||
| bad50e11b1 | |||
| 3d67e5eec8 | |||
|
|
20eccab581 | ||
|
|
e35cacf520 | ||
| a55fca439c | |||
| 9a536970c4 | |||
| 9f1649dc0e | |||
| 06815fc04a | |||
| 27164dc3a7 | |||
| 21634a7a8a | |||
| 77510148e3 | |||
| 4375666084 | |||
| 736328ef0f | |||
| 848f75bafa | |||
| 4b8c0510ca | |||
| 301e6c136c | |||
| 6c89e6ffa6 | |||
| cef764fd0e | |||
| a663faf359 | |||
| 5cf5ef55af | |||
| d98eba97a7 | |||
| 4a914e0aea | |||
| 102cc4d672 | |||
| 2aca4d22ec | |||
| 0f2719a2f0 | |||
|
|
8c153430cd | ||
|
|
7175cd0485 | ||
|
|
c10a46e86f | ||
|
|
d8389c1d9f | ||
| bdef681898 | |||
| 9db8b949ff | |||
| 6819196d20 | |||
| d03937cc92 | |||
| cb42ad82de | |||
| 7a0db7abfb | |||
| de39eac555 | |||
| bab3039d6e | |||
| e285210927 | |||
| dcd7ace830 | |||
| 959964c876 | |||
| f11a7d68e7 | |||
| 359c7c458f | |||
| 58d35a2881 | |||
| 1b57602cb6 | |||
| fa9663f123 | |||
| 856086a271 | |||
| 591b24974c | |||
| 0a258c3807 | |||
| cc7f1ad6af | |||
| 984836d523 | |||
| d15e4ebd53 | |||
| 95dcc69ab4 | |||
| b3254d1e5d | |||
| 992d87412c | |||
| 6a5ba73274 | |||
| 31751c26b9 | |||
| f82a355f01 | |||
|
|
4e14e1d5bf | ||
|
|
5e017ccbb7 | ||
| 445270bb80 | |||
| 9ec5249122 | |||
| 7e3820e6b1 | |||
| 1e678cfd81 | |||
| 2b956bbc3b | |||
| 37e50c14cc | |||
| 718fbc16fa | |||
| 123657431d | |||
| ce1fab884d | |||
|
|
699bfa9af4 | ||
| 5469037de9 | |||
| 81fd0168e7 | |||
| d1da73849f | |||
| 40970c5580 | |||
| cdda10c30c | |||
| 59d3bd61e2 | |||
| 4684b45883 | |||
| e14e43f778 | |||
| 5e86c98afd | |||
|
|
45a4ac84de | ||
|
|
a64eaae57d | ||
| ca0620bd23 | |||
| d52fdb23a1 | |||
| b979e6cfa4 | |||
| eb03054804 | |||
| ef607afcf3 | |||
| 2d8a7034d8 | |||
| fb9ed9949d | |||
| 4875985685 | |||
| a319df0120 | |||
|
|
8e3a3d7c06 | ||
| 0bea975130 | |||
| 553a9340f4 | |||
| eb255d5148 | |||
| 031643c092 | |||
| edb0783e26 | |||
|
|
2f6a4a3ee8 | ||
|
|
7a019730f5 | ||
| 911ec78055 | |||
| 7ca01985b6 | |||
| e60989d318 | |||
|
|
44508faf04 | ||
|
|
59539e4a60 | ||
| e9f3cb8e53 | |||
| ea34d469bb | |||
| 10d7a6476a | |||
| 8226f6cf3d | |||
| c58ff87a4c | |||
| af850fc742 | |||
| dc6b268b74 | |||
| b432c8792c | |||
| b29b76a158 | |||
| 60f8539d3f | |||
| 9f7fedab79 | |||
| 47a8274fd0 | |||
| 929304456a | |||
| 5102fa2ec0 | |||
| 25bb9ede08 | |||
|
|
3d4040e06c | ||
| 7e0e4643e2 | |||
|
|
ac9d2b593e | ||
|
|
016395ef9b | ||
| 849ba0e0d5 | |||
| c70ab4e7e9 | |||
| 149842547c | |||
| b2e5055ed1 | |||
| d35230cb9b | |||
| 9c05cde0b0 | |||
| 079f3a7ff2 | |||
| 7472a8f36e | |||
| a093d368f2 | |||
| 30061ab167 | |||
| 0badbef52c | |||
| 6dc2b8a037 | |||
| 71d92f12b8 | |||
| a05a63ebdc | |||
| f79d94c1b7 | |||
| d72444199f | |||
| fb9e8386fc | |||
| f6f385e597 | |||
| 0cb3b13519 | |||
| dc685f8594 | |||
|
|
b130d844c2 | ||
|
|
624815ba0d | ||
| 71d774bf88 | |||
| 8354770a9e | |||
| 7ccdda6405 | |||
| a4d74bb41b | |||
| 6347b99ba9 | |||
| 2b0df28ab9 | |||
| 16432f94cd | |||
| 4bd21c1662 | |||
| 530ee1dba7 | |||
| 9c54c135b7 | |||
| 936cc7bc92 | |||
|
|
2b6f679f75 | ||
| 1750ba53fd | |||
| cf290b0bc8 | |||
| 3ab5aec767 | |||
| 7286ddc97b | |||
| 3a93aed738 | |||
| d0b75f27fb | |||
| 93cda139de | |||
| 69fa493428 | |||
| b46ce583ad | |||
| 178d53ef14 | |||
| 516b323a1e | |||
|
|
6397de0e76 | ||
| 5dae6c3738 | |||
| 213b020a3d | |||
| 9957dae967 | |||
| e3ae71a161 | |||
| c4c773f929 | |||
| be881b656f | |||
| 9036502f2d | |||
| 3a076b216b | |||
| f24715b5d4 | |||
| 422a331d90 | |||
| 2a3ae5b0be | |||
| cc3bf02b17 | |||
| a8e0eaadbd | |||
| 09fd910b8b | |||
| 2c63b22507 | |||
| f9fb5b7b50 | |||
| 3dd1053eff | |||
| 249d254622 | |||
| 468296e139 | |||
| 3504e09ba2 | |||
| c3e4570f05 | |||
| adaa25dd53 | |||
| 3ac9e56def | |||
| 66b626b3ea | |||
| 61081475f7 | |||
| 88a22c1ed1 | |||
| 1255d30c3a | |||
| afa8d5d62a | |||
| b2f639a461 | |||
| 0d73df5b0e | |||
|
|
50cf8f6fc8 | ||
|
|
8508ce143a | ||
| 1ce58af019 | |||
| dd5580a3a4 | |||
| 9da2a71668 | |||
| e659a385bd | |||
| 05d4fc3fd8 | |||
| 3cd1d0fec4 | |||
| a151046943 | |||
|
|
74c956bf1e | ||
|
|
80272b1ffd | ||
|
|
1dfd6bbdc9 | ||
| 3c1bfbc110 | |||
| 93e3036aff | |||
|
|
aa5cc6aea3 | ||
| 4596f03d88 | |||
|
|
3168a29ecd | ||
|
|
4f84221dea | ||
| c888103fef | |||
| 8feefb0b7e | |||
| 16de0d8533 | |||
| 36211f04bd | |||
| 1276230fcc | |||
| 5c592e95cc | |||
| a41092b896 | |||
| a37bf6e4f7 | |||
| c25e8b2a4a | |||
| 9b8727a63a | |||
| 8853b7ae86 | |||
| 9a543c4b90 | |||
| af29c83eb4 | |||
| 6e87357466 | |||
| 0a6d58cba8 | |||
|
|
4056206af7 | ||
|
|
bddc753e52 | ||
|
|
c464632b1a | ||
| fd962219dd | |||
| e4d6d3ab0d | |||
| be399a1b52 | |||
|
|
7c25ad0452 | ||
|
|
c869d6c44d | ||
| d867a8dda7 | |||
| 1c6071b67c | |||
| fe8723fd8c | |||
|
|
f45862568f | ||
| 32a9ade07c | |||
|
|
7d5a235b95 | ||
|
|
0430be1945 | ||
|
|
f1d2cfea5b | ||
| d4f633fb91 | |||
| 2e6fef951c | |||
| 2315bb5eca | |||
| 13547f8a2a | |||
| 158f8226b0 | |||
|
|
aafa5f5948 | ||
| 17100beb8c | |||
|
|
b74f847f76 | ||
| 8f782e7b17 | |||
| fed817c6d0 | |||
| 0005643c94 | |||
| 87e70d51c1 | |||
| 657d971a2b | |||
| c25af0e186 | |||
| daf5ec9ce2 | |||
| 765cc9c43f | |||
| 08606adae6 | |||
| 68980a8aca | |||
| 4ca3e4eae6 | |||
| 6d78a45ebf | |||
| a0cca5fbca | |||
| e19395f612 | |||
| b2ff662335 | |||
| 351df68762 | |||
| 6f755b8f07 | |||
|
|
a708f28c36 | ||
| 711df07aa6 | |||
| bb7b9c020f | |||
| a0c64bdaa8 | |||
| 22deb24fa2 | |||
| b6a2334fb8 | |||
| 63f12021e4 | |||
| 9f2fe5f3c8 | |||
| 5c1c50bd46 | |||
| c65615465a | |||
| faf4c62328 | |||
| 946aa231a3 | |||
| c6d2904cfe | |||
| 8d3305cd04 | |||
| c6e864d4c4 | |||
| 0923383a42 | |||
| 31bf752970 | |||
| 9d91533922 | |||
| 32884830c8 | |||
| acdec6d2f2 | |||
| 5395dff081 | |||
| bcb8a11d5a | |||
| ab93c5ffe5 | |||
| 904b7afd38 | |||
| 190d5cc19c | |||
| 514545cf65 | |||
| 713b773dc0 | |||
| 387a1fd577 | |||
| 72bbf33317 | |||
| 5412aff4ef | |||
| 16c97ae041 | |||
| ac00777cf1 | |||
| 3d52889924 | |||
| 6e811c9cd4 | |||
| 639c057e0c | |||
| 84c18c80f0 | |||
| f61e50284c | |||
| b85a9a8aa0 | |||
| 8dafc239b3 | |||
| cf462dc4a3 | |||
| c37ba65c80 | |||
| 49c3cae9fa | |||
| aec7bb8c03 | |||
| 806c829ada | |||
| 286dad7021 | |||
| bc607d4828 | |||
| 042e42c2cf | |||
| 8b81998f8c | |||
| 8b5299366f | |||
| a58604478d | |||
| 94985feed8 | |||
|
|
fc5b111c6e | ||
|
|
401d752ac7 | ||
| 5986fe51ff | |||
|
|
65b778d9e5 | ||
|
|
66827d4c87 | ||
|
|
022125759f | ||
| b71fb0992f | |||
| 48dc3d6328 | |||
| 51d892ed29 | |||
| d16c3869d1 | |||
| 8be2e3e232 | |||
| 9302ba216f | |||
| 0ed1ed5623 | |||
| 2bbe4ea29e | |||
| 7641148fce | |||
| f323d312d7 | |||
| 2be0cd341a | |||
| 9c0c851dcb | |||
| 37f35c3a13 | |||
| 42238db254 | |||
| 27e923ecd4 | |||
| 7c610c8487 | |||
| cccf74e2eb | |||
| 4de742d1a3 | |||
| 308e41ac26 | |||
| 72cded314b | |||
| a17d547252 | |||
| df323ffb38 | |||
| 313be228be | |||
| da69eaa00a | |||
| 1a8dfa628b | |||
| 52cee232d0 | |||
| bcf0e3d1bf | |||
| 1cd24aeca9 | |||
| 1fe86746c1 | |||
| 2c475ddbdd | |||
| 5e8542efe5 | |||
| 63930d0966 | |||
| 772cccce89 | |||
| 81b9d659f6 | |||
| 3594e96a87 | |||
| 272ff4f93f | |||
| a47db47489 | |||
| 3034e66c4f | |||
| 09e5b17fe4 | |||
| 2ed020bbcc | |||
| 112de1f7b4 | |||
| c4e5aa1b01 | |||
| bb76dc9ad4 | |||
| ada62ae8ec | |||
| febea389c9 | |||
| 3dc205cb46 | |||
| b8bbaba37f | |||
| 86a67420ea | |||
| 00d87f143f | |||
| 9c35900ce3 | |||
| 18c47cca2c | |||
| 52d25341de | |||
| 60449017d8 | |||
| 71d6639ebd | |||
| 342b5a37f1 | |||
| e94542f15c | |||
| 636ac163dd | |||
| dae19a026f | |||
| acc873afcb | |||
| 15b1cc84fd | |||
| 4aec4e389f | |||
| cd65d80cce | |||
| 96eb4f25ae | |||
|
|
f8a46c30fa | ||
|
|
74e65ec5c6 | ||
| d7b17e491f | |||
|
|
2ee0dc45f0 | ||
| 4e8cc409d4 | |||
| a83f0d7017 | |||
| 8ab41c8eda | |||
| 7fb232d9ae | |||
| 3a37d637ed | |||
| c42c36501a | |||
| 17e88e5d3c | |||
| 5d3a7aa9cc | |||
| 7a005bb161 | |||
| 44b016dc50 | |||
| 5429e5474d | |||
| cb14b3bb78 | |||
| c8823712a4 | |||
| d886bb135f | |||
| ed68a93b20 | |||
| d8a7986120 | |||
| a5a1370135 | |||
| 058a3eabc6 | |||
| 5101fab027 | |||
| 50aa5ca556 | |||
|
|
af627035cd | ||
|
|
4af6e13db4 | ||
| 41ab66eb10 | |||
| dc6989b303 | |||
| 745665782c | |||
|
|
765855b361 | ||
| 2cbcd19b65 | |||
|
|
c029560c4a | ||
| b4733ebfa6 | |||
| a4af9dc1ea | |||
| eda80451a1 | |||
| f8621d2d78 | |||
| d0eb50313a | |||
| d59883b9a6 | |||
| 6cb91a26c9 | |||
| 03ec99dbb4 | |||
| a7f572562e | |||
| bbb8bef37d | |||
| cf3e64deb5 | |||
| 68d961858f | |||
| 37b12b5deb | |||
| 8f693478b8 | |||
| ec8d767809 | |||
|
|
5ab58de70b | ||
| c52d372148 | |||
|
|
6a0144826c | ||
| ec93257720 | |||
|
|
966cc0517b | ||
|
|
2a8d373f67 | ||
| 364d79fafd | |||
| 18b7abdfd5 | |||
| 27ce59316d | |||
| 0295cf6c7a | |||
| 56ae730efa | |||
| a0b3f7fbe5 | |||
| 16e9ce173a | |||
| 99886ad61e | |||
| ee2417cf6f | |||
| 4ec191dcb0 | |||
|
|
4bbe99bda8 | ||
|
|
afd59e580f | ||
| cec871fb70 | |||
| 46dc8123df | |||
| d4ae092ae5 | |||
| 21d4f48109 | |||
|
|
70a4c85149 | ||
|
|
51acb561b8 | ||
|
|
e482538dec | ||
|
|
ec48350c05 | ||
|
|
69c48cecf8 | ||
|
|
46cc507e6d | ||
|
|
98f87553e6 | ||
| 97d20321ed | |||
| 79e33880ac | |||
| 0c67a6b212 | |||
| 46f9385163 | |||
| 59a55e4c88 | |||
| b5b2ac2104 | |||
| aa49e4c075 | |||
| 96e989aa40 | |||
|
|
c8d806b220 | ||
|
|
410da6f541 | ||
| 24d8e14235 | |||
| e6da798e43 | |||
| 00ed6f250b | |||
| 9609cad8f5 | |||
| 80bf9263fd | |||
| 457253f0f6 | |||
| 5c03e631ae | |||
| 5b322d0143 | |||
| 8a49f7169b | |||
| 6dcf9b6836 | |||
| 4bff451dc4 | |||
| e26673b78a | |||
| a8c57b1c53 | |||
| ad12f9b817 | |||
| 5e359bb0ba | |||
| e8457da05f | |||
| 55d37af144 | |||
| 4b120326db | |||
| 2f5f49c69c | |||
|
|
3fefb4b22f | ||
| c9e0650e6a | |||
| a631f5ccb6 | |||
| 01dda7e43e | |||
| ad2aa50d83 | |||
| a3b25c3403 | |||
| 19ec9ad874 | |||
|
|
ead3429912 | ||
|
|
3574ed3a9b | ||
| 9d3ddd3ec1 | |||
| 1d4100a579 | |||
| bca3100a12 | |||
| 001c1cfd95 | |||
| d06733ed4c | |||
| bac3fbef70 | |||
| 7bf548de2b | |||
| 555671ade7 | |||
| c52d914c6e | |||
| a652d09b8d | |||
| 72c020acea | |||
| 976396cd78 | |||
| f4ba3058c7 | |||
| 8b80277884 | |||
| f48a8b31d3 | |||
| 4b8287579c | |||
| f03d35ec8d | |||
| fb297b9a0a | |||
| 553cbd44db | |||
| 56b8917345 | |||
|
|
dbae3380d7 | ||
|
|
35708f65e9 | ||
| aa85de5280 | |||
| 391fe39a2d | |||
|
|
d1af28987b | ||
|
|
b8dad62116 | ||
| d54103943d | |||
| 9b6c6cdfae | |||
|
|
f0c2f0bad2 | ||
|
|
2f261cb0c0 | ||
| 6949236960 | |||
| e961648d85 | |||
| ae789672b4 | |||
| cbde82b0ab | |||
|
|
b2065e7699 | ||
| dc743cee8f | |||
| bb6a796b9f | |||
| 105158e298 | |||
| ef75ad0ef8 | |||
|
|
c0c3295b04 | ||
| 772f465f9d | |||
| cbdbade89f | |||
| 704fe887f1 | |||
| 14764d295c | |||
| 47245e438c | |||
| 0fc7ed857d | |||
| f3b8ee40fa | |||
| 3a66920104 | |||
| d244ba3b97 | |||
| 78258fc9c1 | |||
| d6b0e4994a | |||
| 8da2ab0820 | |||
|
|
f846644145 | ||
|
|
655f0781a2 | ||
| 381c639d18 | |||
| 92c33c9c11 | |||
| c01ed83b40 | |||
|
|
76dd1aaa43 | ||
|
|
9a800d307b | ||
| e3df21f827 | |||
| f5c098546c | |||
| 08722e8b51 | |||
|
|
7f1ef1ace4 | ||
|
|
f52fe72d2a | ||
| d5bba49ca9 | |||
| a7eb2f7598 | |||
| 902d13b273 | |||
| 937f2eb3fe | |||
| adc0c41a67 | |||
| 8412462a8b | |||
| 9f88880ac2 | |||
| 9eda4aa834 | |||
| 234e7f1cf7 | |||
| c40ba2586f | |||
| fc8e85b76c | |||
| a45bba3a7b | |||
| ed27f2ed93 | |||
| 69763dd413 | |||
| 5f648632dd | |||
|
|
95e6f4c0a4 | ||
| 1c7b81c99f | |||
| fe957102d9 | |||
| 034bfb374e | |||
| 71d3a33bf4 | |||
| 0f079080a1 | |||
| 9bf961441b | |||
| 3b96344943 | |||
| 4be94e41a2 | |||
| bd62bf7028 | |||
| 93c8bad986 | |||
| 203ddfbdf3 | |||
| b11424e70e | |||
| 49d85d437b | |||
| 88e2c8ed05 | |||
| 6b56af6119 | |||
| 287ce5c85a | |||
| 737fcf0b30 | |||
| f3dfc50f6e | |||
| 5b74a8ee6b | |||
|
|
77e34d849d | ||
|
|
b6549e06b2 | ||
| abe0ddb731 | |||
| 4a8428f9f7 | |||
|
|
7ff0178b19 | ||
|
|
6d310a6a32 | ||
|
|
ebd84967f2 | ||
| 8de8416160 | |||
| c6b96bec23 | |||
| 82c8b25895 | |||
|
|
2b81cc192e | ||
|
|
028fa7f9bf | ||
| e0d0eaf017 | |||
| 04d49e6aa6 | |||
| e938feec2f | |||
| d4a83c4ec5 | |||
| 3d8e8dfed1 | |||
| ac4449f3f3 | |||
| 159bbf2e6e | |||
|
|
98d5af6d50 | ||
|
|
11a7061e0a | ||
| 61d8c083b2 | |||
| 479f5d342a | |||
| da162ac259 | |||
|
|
c0b38a00b3 | ||
|
|
bee4ee3e74 | ||
|
|
0fb3a0bb2a | ||
|
|
b12b7aaa3a | ||
|
|
cb3e5dfc47 | ||
|
|
77b473c0a1 | ||
|
|
253ddc258a | ||
|
|
cb960baa0c | ||
|
|
51a2782d3c | ||
| d1ea6229a2 | |||
|
|
e717c28758 | ||
| a08c609caa | |||
| 76938af446 | |||
| a3b62db4fa | |||
|
|
21dcdfb3ef | ||
|
|
29ccf72c5e | ||
|
|
4c219ddce9 | ||
|
|
ed721f36d0 | ||
|
|
30414568e0 | ||
| 06812b77af | |||
| bcc91e7609 | |||
| bdcacd5542 | |||
| 51e1f70c24 | |||
| 3c14f5de52 | |||
| 8f4e53c759 | |||
| a1871e105f | |||
| ed69943dc6 | |||
|
|
2897dc26e3 | ||
| 61f1036a24 | |||
|
|
08a29aec26 | ||
|
|
a742fc1f9c | ||
|
|
cf900c3e92 | ||
|
|
e2b8bc19b1 | ||
|
|
14aa4227f4 | ||
|
|
af63891df2 | ||
| 070e9d933f | |||
| e67fc749f4 | |||
|
|
adf9febc8c | ||
|
|
089ffed7af | ||
|
|
990de88caf | ||
| d53d7f4220 | |||
|
|
134becaa93 | ||
| 97f0749f5c | |||
|
|
74b3c6dac7 | ||
| 82210a99c9 | |||
| 76693640cf | |||
| 546133c27e | |||
| 8662a37177 | |||
| defebb21d7 | |||
| 11cf3b8403 | |||
| c869e9d5a3 | |||
|
|
e71c655d05 | ||
|
|
29e8ca25a5 | ||
| 0673567900 | |||
| c42e244f24 | |||
|
|
e940884312 | ||
| 115544edb4 | |||
|
|
9e9bd8f9c4 | ||
| 2da7ea0c78 | |||
| 00a201704b | |||
| 62517870b7 | |||
| 7ff52a7b19 | |||
|
|
597aaa6d42 | ||
|
|
468980abbc | ||
|
|
91ccfcb20e | ||
|
|
c35f3c1adb | ||
|
|
468c7fda6f | ||
|
|
9976ff59d6 | ||
|
|
f548506179 | ||
| d6d626f2d7 | |||
|
|
46f46d734a | ||
| c0cbcd18a2 | |||
| 2e86c793c3 | |||
| d38acba7c9 | |||
|
|
f3a0504d0b | ||
| b8f05a6ff0 | |||
| 0a493884c6 | |||
|
|
a316664b20 | ||
| 804911af3e | |||
|
|
59f9377cc9 | ||
|
|
15cb2cf270 | ||
|
|
1cad8eaf76 | ||
|
|
9400ee7707 | ||
| 22f8cd3fa7 | |||
| e872df680d | |||
|
|
02a0e4a67f | ||
|
|
d524b33f56 | ||
| e7b50ca642 | |||
| 3f40725647 | |||
| aa2685e558 | |||
| f32e772141 | |||
| 7d76929689 | |||
|
|
6c6e5845ef | ||
|
|
001143954e | ||
| 265593f6dd | |||
|
|
ce7c90eb6f | ||
| 2e60e848c8 | |||
|
|
51aaf342fa | ||
|
|
82632b962e | ||
|
|
bfc7b3fe32 | ||
|
|
648c2e208a | ||
| d386c42c08 | |||
| ee6f62b4ba | |||
| de8ba00e55 | |||
|
|
ebbf010e6e | ||
| b7cbc3ec47 | |||
| 8c33611440 | |||
|
|
d8e43b0d81 | ||
| cb87d6cd23 | |||
|
|
8ba79d6a06 | ||
|
|
ba06fde9cc | ||
| 1588b8cda7 | |||
|
|
9c1df779e4 | ||
|
|
2dbbd3a957 | ||
|
|
d165267ec1 | ||
| da8e0856cb | |||
|
|
78ce43969a | ||
|
|
fe10ecf476 | ||
|
|
b16174ec0d | ||
|
|
964d9bdcc7 | ||
|
|
4b6f0b1b06 | ||
|
|
9df16cad02 | ||
|
|
8354508a2b | ||
|
|
ced0eb9fc6 | ||
| 655fef744c | |||
| 0a5b6ce132 | |||
| d6b9634a89 | |||
| 66f1293b95 | |||
| 167740f33d | |||
| 87ef010ae6 | |||
| 7d37deb048 | |||
| 97ca91aa29 | |||
| be54df76f7 | |||
| c2fbc83485 | |||
| 09e985e9b5 | |||
| d10642d6d5 | |||
| 826739fb0e | |||
| 482b6b5477 | |||
| 3509e5fbb6 | |||
| a5fb44ad36 | |||
| c88a07a227 | |||
| 5982a2aa10 | |||
| 3fd81e771d | |||
| c9a53b7159 | |||
| 24ba8bfb14 | |||
| b82901271d | |||
| 25957a37e1 | |||
| 83f0eef9eb | |||
| 46f90aff1f | |||
| 11f69a01bb | |||
| c8f70e0746 | |||
| 2bb1b71a0b | |||
| de5901492b | |||
| 6f5b70342d | |||
| b88299b78d | |||
| 63d8ae092c | |||
| 7b1c9b681e | |||
| 25e8dcc050 | |||
| 3027f47d5d | |||
| 1f723c9ce7 | |||
| 5e9c22a928 | |||
| 54d06f7d51 | |||
| 7f402d5b45 | |||
| 2473cfac17 | |||
| e293d5dfde | |||
| cf9acde872 | |||
| 93b645e9e8 | |||
| 25778a099f | |||
| ba9a49501c | |||
| a2d192084b | |||
| 7fcaaece53 | |||
| cdaeab1d42 | |||
| 47e3ae7d29 | |||
|
|
5597f8ad70 | ||
| 65ec3d5153 | |||
|
|
a7e9aba26c | ||
|
|
592275c0de | ||
| c7de33b8b9 | |||
| 8d645d08dc | |||
| 5e27ab282c | |||
| 75549b66b5 | |||
|
|
437d552b3a | ||
| 553a259c1f | |||
| 97389f7141 | |||
| 11edbe1988 | |||
| 9a9fa522f9 | |||
| 2492b45a66 | |||
|
|
c631c6344e | ||
| 85d9aca6d3 | |||
|
|
0ec917b09f | ||
| a7b9062154 | |||
|
|
25240bc3c2 | ||
|
|
82e3527432 | ||
|
|
7ec9854173 | ||
|
|
b458b4e853 | ||
| 1ba9853a32 | |||
| f1d976521d | |||
|
|
dc8f033e9f | ||
|
|
943fd16e4b | ||
|
|
70c706341e | ||
|
|
c704207d2f | ||
| a7ddfacdd2 | |||
| b51dacf421 | |||
| f25dac0ae3 | |||
| c99deaf93f | |||
| 20cef812ae | |||
| d8341385a4 | |||
| 8d65765daf | |||
| 2f8ca712c9 | |||
|
|
9285caf422 | ||
| f2470b346c | |||
| 1a5f78a970 | |||
| 5979555bcb | |||
| 26047df3c8 | |||
|
|
12936ec1f9 | ||
|
|
1bb92a975e | ||
| f2a083f022 | |||
| 786c35e656 | |||
|
|
88bcf27bfb | ||
|
|
617afb8b1f | ||
|
|
414d6fa0c5 | ||
| 6919dac8f1 | |||
|
|
79d873c135 | ||
|
|
0e8ce8b2e2 | ||
|
|
c4d17d2147 | ||
| e75db5ace1 | |||
| 8848713c72 | |||
| 39d48e61f3 | |||
| e624aa0de0 | |||
| e7410e5373 | |||
| 05dd069e53 | |||
|
|
ad14bf091f | ||
|
|
c34a7fc54d | ||
| ac015123cd | |||
| f7fe0f6528 | |||
| 6b76108d8b | |||
|
|
590040fa1d | ||
|
|
9bb72bfa3a | ||
|
|
8ea6f43ddb | ||
|
|
325c2c1cc0 | ||
|
|
521bfd4303 | ||
|
|
b136bc213c | ||
| b6e70f6eb0 | |||
|
|
3b41952070 | ||
| 84e8af50b8 | |||
|
|
8a5bd9f505 | ||
|
|
509c307e56 | ||
| 11438773a1 | |||
| 2e762537fc | |||
| 57446fa6d8 | |||
| 1fa02de62f | |||
| 06fa1766d6 | |||
| 63297328ed | |||
| 7c15da2b4c | |||
|
|
f4e7896bf8 | ||
| 3432d64a3c | |||
| 9be21fc2b2 | |||
|
|
2537240bb0 | ||
|
|
c467fc7fa8 | ||
|
|
10c2668a67 | ||
|
|
63db694efa | ||
|
|
f915dab239 | ||
|
|
9f76fa97c5 | ||
|
|
99a584f8cf | ||
|
|
16423de079 | ||
|
|
e99a1a3854 | ||
|
|
8ca01757c9 | ||
|
|
15ff2989f4 | ||
|
|
83b27582f7 | ||
|
|
9e66b3ef7d | ||
|
|
ae2171c633 | ||
|
|
f9766a7d21 | ||
|
|
83038a7c81 | ||
|
|
3df466a717 | ||
|
|
b3af3cd0e0 |
@ -1,11 +1,36 @@
|
||||
NEXT_PUBLIC_API_SERVER_PATH="http://1.248.227.176:38080"
|
||||
NEXT_PUBLIC_RUN_MODE="development"
|
||||
|
||||
NEXT_PUBLIC_HOST_URL="http://1.248.227.176:4000"
|
||||
NEXT_PUBLIC_API_SERVER_PATH="https://dev-api.hanasys.jp"
|
||||
|
||||
NEXT_PUBLIC_HOST_URL="//1.248.227.176:4000"
|
||||
|
||||
NEXT_PUBLIC_API_HOST_URL="https://dev.hanasys.jp"
|
||||
|
||||
SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y="
|
||||
|
||||
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_bV5zuYMyyIYFlOb3"
|
||||
NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_yAS4QDalL9jgQ7vS"
|
||||
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_yAS4QDalL9jgQ7vS"
|
||||
NEXT_PUBLIC_CONVERTER_DWG_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_a0FLEK6M2oTpXInK"
|
||||
NEXT_PUBLIC_CONVERTER_DXF_API_URL="https://v2.convertapi.com/convert/dxf/to/png?Secret=secret_a0FLEK6M2oTpXInK"
|
||||
|
||||
NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-stg.q-cells.jp:8120/eos/login/autoLogin"
|
||||
NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin"
|
||||
NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin"
|
||||
|
||||
# 테스트용
|
||||
# AWS_REGION="ap-northeast-2"
|
||||
# AMPLIFY_BUCKET="interplug"
|
||||
# AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR"
|
||||
# AWS_SECRET_ACCESS_KEY="NDzSvPUo4/ErpPOEs1eZAnoUBilc1FL7YaoHkqe4"
|
||||
# NEXT_PUBLIC_AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com"
|
||||
|
||||
# 실제 일본 서버
|
||||
AWS_REGION="ap-northeast-1"
|
||||
AMPLIFY_BUCKET="files.hanasys.jp"
|
||||
AWS_ACCESS_KEY_ID="AKIA3K4QWLZHFZRJOM2E"
|
||||
AWS_SECRET_ACCESS_KEY="Cw87TjKwnTWRKgORGxYiFU6GUTgu25eUw4eLBNcA"
|
||||
NEXT_PUBLIC_AWS_S3_BASE_URL="//files.hanasys.jp"
|
||||
|
||||
S3_PROFILE="dev"
|
||||
|
||||
#logging
|
||||
NEXT_PUBLIC_ENABLE_LOGGING=true
|
||||
32
.env.local.dev
Normal file
@ -0,0 +1,32 @@
|
||||
NEXT_PUBLIC_RUN_MODE="local.dev"
|
||||
|
||||
NEXT_PUBLIC_API_SERVER_PATH="https://dev-api.hanasys.jp"
|
||||
|
||||
NEXT_PUBLIC_HOST_URL="//1.248.227.176:4000"
|
||||
|
||||
NEXT_PUBLIC_API_HOST_URL="http://1.248.227.176:5000"
|
||||
|
||||
SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y="
|
||||
|
||||
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_bV5zuYMyyIYFlOb3"
|
||||
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_yAS4QDalL9jgQ7vS"
|
||||
NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_a0FLEK6M2oTpXInK"
|
||||
|
||||
NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-stg.q-cells.jp:8120/eos/login/autoLogin"
|
||||
NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin"
|
||||
|
||||
# 테스트용
|
||||
# AWS_REGION="ap-northeast-2"
|
||||
# AMPLIFY_BUCKET="interplug"
|
||||
# AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR"
|
||||
# AWS_SECRET_ACCESS_KEY="NDzSvPUo4/ErpPOEs1eZAnoUBilc1FL7YaoHkqe4"
|
||||
# NEXT_PUBLIC_AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com"
|
||||
|
||||
# 실제 일본 서버
|
||||
AWS_REGION="ap-northeast-1"
|
||||
AMPLIFY_BUCKET="files.hanasys.jp"
|
||||
AWS_ACCESS_KEY_ID="AKIA3K4QWLZHFZRJOM2E"
|
||||
AWS_SECRET_ACCESS_KEY="Cw87TjKwnTWRKgORGxYiFU6GUTgu25eUw4eLBNcA"
|
||||
NEXT_PUBLIC_AWS_S3_BASE_URL="//files.hanasys.jp"
|
||||
|
||||
S3_PROFILE="dev"
|
||||
36
.env.localhost
Normal file
@ -0,0 +1,36 @@
|
||||
NEXT_PUBLIC_RUN_MODE="local"
|
||||
|
||||
NEXT_PUBLIC_API_SERVER_PATH="https://dev-api.hanasys.jp"
|
||||
|
||||
NEXT_PUBLIC_HOST_URL="//1.248.227.176:4000"
|
||||
|
||||
NEXT_PUBLIC_API_HOST_URL="http://localhost:3000"
|
||||
|
||||
SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y="
|
||||
|
||||
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_bV5zuYMyyIYFlOb3"
|
||||
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_yAS4QDalL9jgQ7vS"
|
||||
NEXT_PUBLIC_CONVERTER_DWG_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_a0FLEK6M2oTpXInK"
|
||||
NEXT_PUBLIC_CONVERTER_DXF_API_URL="https://v2.convertapi.com/convert/dxf/to/png?Secret=secret_a0FLEK6M2oTpXInK"
|
||||
|
||||
NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-stg.q-cells.jp:8120/eos/login/autoLogin"
|
||||
NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin"
|
||||
|
||||
# 테스트용
|
||||
# AWS_REGION="ap-northeast-2"
|
||||
# AMPLIFY_BUCKET="interplug"
|
||||
# AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR"
|
||||
# AWS_SECRET_ACCESS_KEY="NDzSvPUo4/ErpPOEs1eZAnoUBilc1FL7YaoHkqe4"
|
||||
# NEXT_PUBLIC_AWS_S3_BASE_URL="https://interplug.s3.ap-northeast-2.amazonaws.com"
|
||||
|
||||
# 실제 일본 서버
|
||||
AWS_REGION="ap-northeast-1"
|
||||
AMPLIFY_BUCKET="files.hanasys.jp"
|
||||
AWS_ACCESS_KEY_ID="AKIA3K4QWLZHFZRJOM2E"
|
||||
AWS_SECRET_ACCESS_KEY="Cw87TjKwnTWRKgORGxYiFU6GUTgu25eUw4eLBNcA"
|
||||
NEXT_PUBLIC_AWS_S3_BASE_URL="//files.hanasys.jp"
|
||||
|
||||
S3_PROFILE="dev"
|
||||
|
||||
#logging
|
||||
NEXT_PUBLIC_ENABLE_LOGGING=false
|
||||
@ -1,13 +1,35 @@
|
||||
NEXT_PUBLIC_RUN_MODE="production"
|
||||
|
||||
NEXT_PUBLIC_API_SERVER_PATH="https://api.hanasys.jp/"
|
||||
|
||||
NEXT_PUBLIC_HOST_URL="http://1.248.227.176:4000"
|
||||
NEXT_PUBLIC_HOST_URL="//1.248.227.176:4000"
|
||||
|
||||
NEXT_PUBLIC_API_HOST_URL="https://www.hanasys.jp"
|
||||
|
||||
SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y="
|
||||
|
||||
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_bV5zuYMyyIYFlOb3"
|
||||
NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_yAS4QDalL9jgQ7vS"
|
||||
# NEXT_PUBLIC_CONVERTER_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_yAS4QDalL9jgQ7vS"
|
||||
NEXT_PUBLIC_CONVERTER_DWG_API_URL="https://v2.convertapi.com/convert/dwg/to/png?Secret=secret_a0FLEK6M2oTpXInK"
|
||||
NEXT_PUBLIC_CONVERTER_DXF_API_URL="https://v2.convertapi.com/convert/dxf/to/png?Secret=secret_a0FLEK6M2oTpXInK"
|
||||
|
||||
# NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="https://q-order.q-cells.jp/eos/login/autoLogin"
|
||||
# NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="https://q-musubi.q-cells.jp/qm/login/autoLogin"
|
||||
NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="http://q-order-stg.q-cells.jp:8120/eos/login/autoLogin"
|
||||
NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="http://q-musubi-stg.q-cells.jp:8120/qm/login/autoLogin"
|
||||
NEXT_PUBLIC_Q_ORDER_AUTO_LOGIN_URL="https://q-order.q-cells.jp/eos/login/autoLogin"
|
||||
NEXT_PUBLIC_Q_MUSUBI_AUTO_LOGIN_URL="https://q-musubi.q-cells.jp/qm/login/autoLogin"
|
||||
|
||||
# AWS_REGION="ap-northeast-2"
|
||||
# AMPLIFY_BUCKET="interplug"
|
||||
# AWS_ACCESS_KEY_ID="AKIAVWMWJCUXFHEAZ4FR"
|
||||
# AWS_SECRET_ACCESS_KEY="NDzSvPUo4/ErpPOEs1eZAnoUBilc1FL7YaoHkqe4"
|
||||
# NEXT_PUBLIC_AWS_S3_BASE_URL="//files.hanasys.jp"
|
||||
|
||||
# 실제 일본 서버
|
||||
AWS_REGION="ap-northeast-1"
|
||||
AMPLIFY_BUCKET="files.hanasys.jp"
|
||||
AWS_ACCESS_KEY_ID="AKIA3K4QWLZHFZRJOM2E"
|
||||
AWS_SECRET_ACCESS_KEY="Cw87TjKwnTWRKgORGxYiFU6GUTgu25eUw4eLBNcA"
|
||||
NEXT_PUBLIC_AWS_S3_BASE_URL="//files.hanasys.jp"
|
||||
|
||||
S3_PROFILE="prd"
|
||||
|
||||
#logging
|
||||
NEXT_PUBLIC_ENABLE_LOGGING=false
|
||||
3
.gitmessage.txt
Normal file
@ -0,0 +1,3 @@
|
||||
[일감번호] : [제목]
|
||||
|
||||
[작업내용] :
|
||||
@ -1,6 +1,6 @@
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
### Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
@ -34,3 +34,5 @@ You can check out [the Next.js GitHub repository](https://github.com/vercel/next
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
|
||||
deploy test
|
||||
|
||||
13
dev.ecosystem.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'qcast-front-development',
|
||||
script: 'node_modules/next/dist/bin/next',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
env: {
|
||||
PORT: 5010,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
13
dev.local.ecosystem.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'qcast-front-local-development',
|
||||
script: 'node_modules/next/dist/bin/next',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
env: {
|
||||
PORT: 5000,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
13
ecosystem.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'qcast-front-production',
|
||||
script: 'node_modules/next/dist/bin/next',
|
||||
instances: 2,
|
||||
exec_mode: 'cluster',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
23
package.json
@ -3,27 +3,34 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start -p 5000",
|
||||
"start:dev": "next start -p 5010",
|
||||
"dev": "env-cmd -f .env.localhost next dev",
|
||||
"local:dev": "env-cmd -f .env.local.dev next dev",
|
||||
"build": "env-cmd -f .env.production next build",
|
||||
"build:dev": "env-cmd -f .env.development next build",
|
||||
"build:local.dev": "env-cmd -f .env.local.dev next build",
|
||||
"start:cluster1": "env-cmd -f .env.production next start -p 5000",
|
||||
"start:cluster2": "env-cmd -f .env.production next start -p 5001",
|
||||
"start:dev": "env-cmd -f .env.development next start -p 5010",
|
||||
"lint": "next lint",
|
||||
"serve": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.772.0",
|
||||
"ag-grid-react": "^32.0.2",
|
||||
"axios": "^1.7.8",
|
||||
"big.js": "^6.2.2",
|
||||
"chart.js": "^4.4.6",
|
||||
"dayjs": "^1.11.13",
|
||||
"env-cmd": "^10.1.0",
|
||||
"fabric": "^5.3.0",
|
||||
"framer-motion": "^11.2.13",
|
||||
"fs": "^0.0.1-security",
|
||||
"iron-session": "^8.0.2",
|
||||
"jimp": "^1.6.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"mathjs": "^13.0.2",
|
||||
"mssql": "^11.0.1",
|
||||
"next": "14.2.21",
|
||||
"next": "14.2.28",
|
||||
"next-international": "^1.2.4",
|
||||
"react": "^18",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
@ -37,6 +44,7 @@
|
||||
"react-responsive-modal": "^6.4.2",
|
||||
"react-select": "^5.8.1",
|
||||
"recoil": "^0.7.7",
|
||||
"sharp": "^0.33.5",
|
||||
"sqlite": "^5.1.1",
|
||||
"sqlite3": "^5.1.7",
|
||||
"sweetalert2": "^11.14.1",
|
||||
@ -47,11 +55,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@turf/turf": "^7.0.0",
|
||||
"@types/node": "^24.3.0",
|
||||
"@types/react": "^19.1.11",
|
||||
"convertapi": "^1.14.0",
|
||||
"postcss": "^8",
|
||||
"prettier": "^3.3.3",
|
||||
"react-color-palette": "^7.2.2",
|
||||
"sass": "^1.77.8",
|
||||
"tailwindcss": "^3.4.1"
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
13
prd1.ecosystem.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'qcast-front-production-1',
|
||||
script: 'node_modules/next/dist/bin/next',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
env: {
|
||||
PORT: 5000,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
13
prd2.ecosystem.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'qcast-front-production-2',
|
||||
script: 'node_modules/next/dist/bin/next',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
env: {
|
||||
PORT: 5001,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
BIN
public/static/images/canvas/deg/-105.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/static/images/canvas/deg/-120.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
public/static/images/canvas/deg/-135.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/static/images/canvas/deg/-15.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/static/images/canvas/deg/-150.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
public/static/images/canvas/deg/-165.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/static/images/canvas/deg/-30.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
public/static/images/canvas/deg/-45.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
public/static/images/canvas/deg/-60.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
public/static/images/canvas/deg/-75.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
public/static/images/canvas/deg/-90.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
public/static/images/canvas/deg/0.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/static/images/canvas/deg/105.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
public/static/images/canvas/deg/120.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
public/static/images/canvas/deg/135.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
public/static/images/canvas/deg/15.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/static/images/canvas/deg/150.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
public/static/images/canvas/deg/165.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/static/images/canvas/deg/180.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
public/static/images/canvas/deg/30.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
public/static/images/canvas/deg/45.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/static/images/canvas/deg/60.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
public/static/images/canvas/deg/75.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/static/images/canvas/deg/90.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
3
public/static/images/canvas/hide-check-arr.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 0L15.7942 13.5H0.205771L8 0Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 160 B |
BIN
public/static/images/canvas/roof_warning_correct.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/static/images/canvas/roof_warning_wrong.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 11 KiB |
4
public/static/images/sub/oneonone_profile_icon.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="8.25" stroke="#101010" stroke-width="1.5"/>
|
||||
<path d="M7.94995 16.5C10.0485 14.302 13.9289 14.1986 16.05 16.5M14.2455 9.75C14.2455 10.9926 13.2367 12 11.9923 12C10.7479 12 9.73912 10.9926 9.73912 9.75C9.73912 8.50736 10.7479 7.5 11.9923 7.5C13.2367 7.5 14.2455 8.50736 14.2455 9.75Z" stroke="#101010" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 474 B |
@ -1,21 +1,13 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, useEffect, useState } from 'react'
|
||||
import { useLocalStorage } from 'usehooks-ts'
|
||||
import { createContext, useState } from 'react'
|
||||
|
||||
export const GlobalDataContext = createContext(null)
|
||||
|
||||
const GlobalDataProvider = ({ children }) => {
|
||||
const [managementState, setManagementState] = useState(null)
|
||||
const [managementStateLoaded, setManagementStateLoaded] = useLocalStorage('managementStateLoaded', null)
|
||||
|
||||
useEffect(() => {
|
||||
if (managementState !== null) {
|
||||
setManagementStateLoaded(managementState)
|
||||
}
|
||||
}, [managementState])
|
||||
|
||||
return <GlobalDataContext.Provider value={{ managementState, setManagementState, managementStateLoaded }}>{children}</GlobalDataContext.Provider>
|
||||
return <GlobalDataContext.Provider value={{ managementState, setManagementState }}>{children}</GlobalDataContext.Provider>
|
||||
}
|
||||
|
||||
export default GlobalDataProvider
|
||||
|
||||
70
src/app/api/image/cad/route.js
Normal file
@ -0,0 +1,70 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'
|
||||
|
||||
const Bucket = process.env.AMPLIFY_BUCKET
|
||||
const s3 = new S3Client({
|
||||
region: process.env.AWS_REGION,
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
||||
},
|
||||
})
|
||||
|
||||
const uploadImage = async (file) => {
|
||||
console.log('🚀 ~ uploadImage ~ file:', file)
|
||||
const Body = Buffer.from(await file.arrayBuffer())
|
||||
const Key = `cads/${file.name}`
|
||||
const ContentType = 'image/png'
|
||||
|
||||
await s3.send(
|
||||
new PutObjectCommand({
|
||||
Bucket,
|
||||
Key,
|
||||
Body,
|
||||
ContentType,
|
||||
}),
|
||||
)
|
||||
|
||||
return {
|
||||
filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`,
|
||||
fileName: Key,
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(req) {
|
||||
try {
|
||||
const formData = await req.formData()
|
||||
const file = formData.get('file')
|
||||
|
||||
const result = await uploadImage(file)
|
||||
|
||||
return NextResponse.json(result)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return NextResponse.json({ error: 'Failed to upload image' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(req) {
|
||||
try {
|
||||
const searchParams = req.nextUrl.searchParams
|
||||
const Key = `cads/${searchParams.get('fileName')}`
|
||||
console.log('🚀 ~ DELETE ~ Key:', Key)
|
||||
|
||||
if (!Key) {
|
||||
return NextResponse.json({ error: 'fileName parameter is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
await s3.send(
|
||||
new DeleteObjectCommand({
|
||||
Bucket,
|
||||
Key,
|
||||
}),
|
||||
)
|
||||
|
||||
return NextResponse.json({ message: '이미지 삭제 성공' }, { status: 200 })
|
||||
} catch (error) {
|
||||
console.error('S3 Delete Error:', error)
|
||||
return NextResponse.json({ error: 'Failed to delete image' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
203
src/app/api/image/canvas/route.js
Normal file
@ -0,0 +1,203 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { Jimp } from 'jimp'
|
||||
|
||||
const Bucket = process.env.AMPLIFY_BUCKET
|
||||
const s3 = new S3Client({
|
||||
region: process.env.AWS_REGION,
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
||||
},
|
||||
})
|
||||
|
||||
const checkArea = (obj) => {
|
||||
const { width, height, left, top } = obj
|
||||
|
||||
if (left < 0 || top < 0 || width > 1600 || height > 1000) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const cropImage = async (Key, width, height, left, top) => {
|
||||
try {
|
||||
// Get the image from S3
|
||||
const { Body } = await s3.send(
|
||||
new GetObjectCommand({
|
||||
Bucket,
|
||||
Key,
|
||||
}),
|
||||
)
|
||||
|
||||
const chunks = []
|
||||
for await (const chunk of Body) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
const buffer = Buffer.concat(chunks)
|
||||
|
||||
let image = await Jimp.read(buffer)
|
||||
image.autocrop({ tolerance: 0.0002, leaveBorder: 10 })
|
||||
|
||||
const resizedImage = await resizeImage(image).then((result) => {
|
||||
return result
|
||||
})
|
||||
|
||||
return await resizedImage.getBuffer('image/png')
|
||||
|
||||
// Convert stream to buffer
|
||||
// const chunks = []
|
||||
// for await (const chunk of Body) {
|
||||
// chunks.push(chunk)
|
||||
// }
|
||||
// const imageBuffer = Buffer.concat(chunks)
|
||||
|
||||
// const image = await Jimp.read(Body)
|
||||
|
||||
// if (!checkResult) {
|
||||
// processedImage = await image.toBuffer()
|
||||
// }
|
||||
|
||||
//let processedImage
|
||||
// if (!checkResult) {
|
||||
// processedImage = await sharp(imageBuffer).toBuffer()
|
||||
// } else {
|
||||
// processedImage = await sharp(imageBuffer)
|
||||
// .extract({
|
||||
// width: parseInt(width),
|
||||
// height: parseInt(height),
|
||||
// left: parseInt(left),
|
||||
// top: parseInt(top),
|
||||
// })
|
||||
// .png()
|
||||
// .toBuffer()
|
||||
// }
|
||||
// return processedImage
|
||||
} catch (error) {
|
||||
console.error('Error processing image:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
//크롭된 이미지를 배경 크기에 맞게 리사이즈
|
||||
const resizeImage = async (image) => {
|
||||
//엑셀 템플릿 너비 35.4cm, 높이 12.89cm
|
||||
const convertStandardWidth = Math.round((35.4 * 96) / 2.54)
|
||||
const convertStandardHeight = Math.round((12.89 * 96) / 2.54)
|
||||
|
||||
// 이미지를 배경의 98%까지 확대 (훨씬 더 크게)
|
||||
const targetImageWidth = convertStandardWidth * 0.98
|
||||
const targetImageHeight = convertStandardHeight * 0.98
|
||||
|
||||
const scaleX = targetImageWidth / image.bitmap.width
|
||||
const scaleY = targetImageHeight / image.bitmap.height
|
||||
let scale = Math.min(scaleX, scaleY) // 비율 유지하면서 최대한 크게
|
||||
|
||||
// scale 저장 (나중에 전체 확대에 사용)
|
||||
const originalScale = scale
|
||||
|
||||
let finalWidth = Math.round(image.bitmap.width * scale)
|
||||
let finalHeight = Math.round(image.bitmap.height * scale)
|
||||
|
||||
if (scale >= 0.6) {
|
||||
// 실제 리사이즈 실행
|
||||
image.resize({ w: finalWidth, h: finalHeight })
|
||||
}
|
||||
|
||||
//배경 이미지를 생성
|
||||
const mixedImage = new Jimp({ width: convertStandardWidth, height: convertStandardHeight, color: 0xffffffff })
|
||||
|
||||
//이미지를 중앙에 배치
|
||||
const x = Math.floor((mixedImage.bitmap.width - image.bitmap.width) / 2)
|
||||
const y = Math.floor((mixedImage.bitmap.height - image.bitmap.height) / 2)
|
||||
|
||||
//이미지를 배경 이미지에 합성
|
||||
mixedImage.composite(image, x, y, {
|
||||
opacitySource: 1, // 원본 투명도 유지
|
||||
opacityDest: 1,
|
||||
})
|
||||
|
||||
// scale이 0.8 이하인 경우 완성된 이미지를 전체적으로 확대
|
||||
if (originalScale <= 0.8) {
|
||||
const enlargeRatio = 1.5 // 50% 확대
|
||||
const newWidth = Math.round(mixedImage.bitmap.width * enlargeRatio)
|
||||
const newHeight = Math.round(mixedImage.bitmap.height * enlargeRatio)
|
||||
|
||||
mixedImage.resize({ w: newWidth, h: newHeight })
|
||||
}
|
||||
|
||||
return mixedImage
|
||||
}
|
||||
|
||||
export async function POST(req) {
|
||||
try {
|
||||
const formData = await req.formData()
|
||||
const file = formData.get('file')
|
||||
const objectNo = formData.get('objectNo')
|
||||
const planNo = formData.get('planNo')
|
||||
const type = formData.get('type')
|
||||
const width = formData.get('width')
|
||||
const height = formData.get('height')
|
||||
const left = formData.get('left')
|
||||
const top = formData.get('top')
|
||||
|
||||
const OriginalKey = `Drawing/${uuidv4()}`
|
||||
|
||||
/**
|
||||
* 원본 이미지를 우선 저장한다.
|
||||
* 이미지 이름이 겹지는 현상을 방지하기 위해 uuid 를 사용한다.
|
||||
*/
|
||||
await s3.send(
|
||||
new PutObjectCommand({
|
||||
Bucket,
|
||||
Key: OriginalKey,
|
||||
Body: Buffer.from(await file.arrayBuffer()),
|
||||
ContentType: 'image/png',
|
||||
}),
|
||||
)
|
||||
|
||||
/**
|
||||
* 저장된 원본 이미지를 기준으로 크롭여부를 결정하여 크롭 이미지를 저장한다.
|
||||
*/
|
||||
const bufferImage = await cropImage(OriginalKey, width, height, left, top)
|
||||
|
||||
/**
|
||||
* 크롭 이미지 이름을 결정한다.
|
||||
*/
|
||||
const Key = `Drawing/${process.env.S3_PROFILE}/${objectNo}_${planNo}_${type}.png`
|
||||
|
||||
/**
|
||||
* 크롭이 완료된 이미지를 업로드한다.
|
||||
*/
|
||||
await s3.send(
|
||||
new PutObjectCommand({
|
||||
Bucket,
|
||||
Key,
|
||||
Body: bufferImage,
|
||||
ContentType: 'image/png',
|
||||
}),
|
||||
)
|
||||
|
||||
/**
|
||||
* 크롭이미지 저장이 완료되면 원본 이미지를 삭제한다.
|
||||
*/
|
||||
await s3.send(
|
||||
new DeleteObjectCommand({
|
||||
Bucket,
|
||||
Key: OriginalKey,
|
||||
}),
|
||||
)
|
||||
|
||||
const result = {
|
||||
filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`,
|
||||
fileName: Key,
|
||||
}
|
||||
|
||||
return NextResponse.json(result)
|
||||
} catch (error) {
|
||||
console.error('Error in POST:', error)
|
||||
return NextResponse.json({ error: 'Failed to process and upload image' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
59
src/app/api/image/estimate-image-copy/route.js
Normal file
@ -0,0 +1,59 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { S3Client, CopyObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3'
|
||||
import sharp from 'sharp'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
const Bucket = process.env.AMPLIFY_BUCKET
|
||||
const s3 = new S3Client({
|
||||
region: process.env.AWS_REGION,
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
||||
},
|
||||
})
|
||||
|
||||
export async function POST(req) {
|
||||
const { objectNo, planNo, newObjectNo, newPlanNo } = await req.json()
|
||||
|
||||
const responseArray = []
|
||||
|
||||
//견적서1 번 이미지
|
||||
const isExistImage1 = await s3.send(
|
||||
new GetObjectCommand({
|
||||
Bucket,
|
||||
Key: `Drawing/${process.env.S3_PROFILE}/${objectNo}_${planNo}_1.png`,
|
||||
}),
|
||||
)
|
||||
|
||||
//견적서2 번 이미지
|
||||
const isExistImage2 = await s3.send(
|
||||
new GetObjectCommand({
|
||||
Bucket,
|
||||
Key: `Drawing/${process.env.S3_PROFILE}/${objectNo}_${planNo}_2.png`,
|
||||
}),
|
||||
)
|
||||
|
||||
//견적서1,2 번 이미지 둘다 있어야함
|
||||
if (isExistImage1.$metadata.httpStatusCode === 200 && isExistImage2.$metadata.httpStatusCode === 200) {
|
||||
//견적서1 번 이미지 복사
|
||||
const copyCommand = new CopyObjectCommand({
|
||||
Bucket,
|
||||
CopySource: encodeURI(`${Bucket}/Drawing/${process.env.S3_PROFILE}/${objectNo}_${planNo}_1.png`),
|
||||
Key: `Drawing/${process.env.S3_PROFILE}/${newObjectNo}_${newPlanNo}_1.png`,
|
||||
})
|
||||
|
||||
const response = await s3.send(copyCommand)
|
||||
|
||||
const copyCommand2 = new CopyObjectCommand({
|
||||
Bucket,
|
||||
CopySource: encodeURI(`${Bucket}/Drawing/${process.env.S3_PROFILE}/${objectNo}_${planNo}_2.png`),
|
||||
Key: `Drawing/${process.env.S3_PROFILE}/${newObjectNo}_${newPlanNo}_2.png`,
|
||||
})
|
||||
|
||||
const response2 = await s3.send(copyCommand2)
|
||||
|
||||
responseArray.push(response, response2)
|
||||
return NextResponse.json({ message: '견적서 이미지 복사 성공', responseArray }, { status: 200 })
|
||||
} else {
|
||||
return NextResponse.json({ message: '견적서 이미지 복사 실패(존재하지 않는 이미지)', responseArray }, { status: 400 })
|
||||
}
|
||||
}
|
||||
78
src/app/api/image/map/route.js
Normal file
@ -0,0 +1,78 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'
|
||||
|
||||
const Bucket = process.env.AMPLIFY_BUCKET
|
||||
const s3 = new S3Client({
|
||||
region: process.env.AWS_REGION,
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
||||
},
|
||||
})
|
||||
|
||||
export async function GET(req) {
|
||||
try {
|
||||
const searchParams = req.nextUrl.searchParams
|
||||
const q = searchParams.get('q')
|
||||
const fileNm = searchParams.get('fileNm')
|
||||
const zoom = searchParams.get('zoom')
|
||||
|
||||
/** 구글 맵을 이미지로 변경하기 위한 API */
|
||||
const API_KEY = 'AIzaSyDO7nVR1N_D2tKy60hgGFavpLaXkHpiHpc'
|
||||
const targetUrl = `https://maps.googleapis.com/maps/api/staticmap?center=${q}&zoom=${zoom}&maptype=satellite&size=640x640&scale=1&key=${API_KEY}`
|
||||
const decodeUrl = decodeURIComponent(targetUrl)
|
||||
|
||||
/** 구글 맵을 이미지로 변경하기 위한 API 호출 */
|
||||
const response = await fetch(decodeUrl)
|
||||
const data = await response.arrayBuffer()
|
||||
// const buffer = Buffer.from(data)
|
||||
|
||||
/** 변경된 이미지를 S3에 업로드 */
|
||||
const Body = Buffer.from(data)
|
||||
const Key = `maps/${fileNm}`
|
||||
const ContentType = 'image/png'
|
||||
|
||||
await s3.send(
|
||||
new PutObjectCommand({
|
||||
Bucket,
|
||||
Key,
|
||||
Body,
|
||||
ContentType,
|
||||
}),
|
||||
)
|
||||
|
||||
const result = {
|
||||
filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`,
|
||||
fileName: Key,
|
||||
}
|
||||
|
||||
return NextResponse.json(result)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return NextResponse.json({ error: 'Failed to upload image' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(req) {
|
||||
try {
|
||||
const searchParams = req.nextUrl.searchParams
|
||||
const Key = `maps/${searchParams.get('fileName')}`
|
||||
console.log('🚀 ~ DELETE ~ Key:', Key)
|
||||
|
||||
if (!Key) {
|
||||
return NextResponse.json({ error: 'fileName parameter is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
await s3.send(
|
||||
new DeleteObjectCommand({
|
||||
Bucket,
|
||||
Key,
|
||||
}),
|
||||
)
|
||||
|
||||
return NextResponse.json({ message: '이미지 삭제 성공' }, { status: 200 })
|
||||
} catch (error) {
|
||||
console.error('S3 Delete Error:', error)
|
||||
return NextResponse.json({ error: 'Failed to delete image' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
72
src/app/api/image/upload/route.js
Normal file
@ -0,0 +1,72 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'
|
||||
|
||||
const Bucket = process.env.AMPLIFY_BUCKET
|
||||
const s3 = new S3Client({
|
||||
region: process.env.AWS_REGION,
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
||||
},
|
||||
})
|
||||
|
||||
const uploadImage = async (file) => {
|
||||
const Body = Buffer.from(await file.arrayBuffer())
|
||||
const Key = `upload/${file.name}`
|
||||
const ContentType = file.ContentType
|
||||
|
||||
await s3.send(
|
||||
new PutObjectCommand({
|
||||
Bucket,
|
||||
Key,
|
||||
Body,
|
||||
ContentType,
|
||||
}),
|
||||
)
|
||||
|
||||
return {
|
||||
filePath: `https://${process.env.AMPLIFY_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${Key}`,
|
||||
fileName: Key,
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(req) {
|
||||
try {
|
||||
const formData = await req.formData()
|
||||
const file = formData.get('file')
|
||||
|
||||
const result = await uploadImage(file)
|
||||
result.message = '이미지 업로드 성공'
|
||||
|
||||
return NextResponse.json(result)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return NextResponse.json({ error: 'Failed to upload image' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(req) {
|
||||
try {
|
||||
const searchParams = req.nextUrl.searchParams
|
||||
const fileName = searchParams.get('fileName')
|
||||
|
||||
if (!fileName) {
|
||||
return NextResponse.json({ error: 'fileName parameter is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const Key = `upload/${fileName}`
|
||||
console.log('🚀 ~ DELETE ~ Key:', Key)
|
||||
|
||||
await s3.send(
|
||||
new DeleteObjectCommand({
|
||||
Bucket,
|
||||
Key,
|
||||
}),
|
||||
)
|
||||
|
||||
return NextResponse.json({ message: '이미지 삭제 성공' }, { status: 200 })
|
||||
} catch (error) {
|
||||
console.error('S3 Delete Error:', error)
|
||||
return NextResponse.json({ error: 'Failed to delete image' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
9
src/app/community/qna/page.jsx
Normal file
@ -0,0 +1,9 @@
|
||||
import Qna from '@/components/community/Qna'
|
||||
|
||||
export default async function CommunityQnaPage() {
|
||||
return (
|
||||
<>
|
||||
<Qna />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -21,6 +21,8 @@ const defaultEstimateData = {
|
||||
fileList: [],
|
||||
fileFlg: '0', //후일 자료 제출 (체크 1 노체크 0)
|
||||
priceCd: '',
|
||||
pricingFlag: false, // 가격 처리 플래그 추가
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -45,8 +47,19 @@ const FloorPlanProvider = ({ children }) => {
|
||||
// const pathname = usePathname()
|
||||
// const setCorrentObjectNo = useSetRecoilState(correntObjectNoState)
|
||||
const searchParams = useSearchParams()
|
||||
const path = usePathname()
|
||||
const objectNo = searchParams.get('objectNo')
|
||||
const pid = searchParams.get('pid')
|
||||
|
||||
useEffect(() => {
|
||||
setFloorPlanState((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
objectNo,
|
||||
pid,
|
||||
}
|
||||
})
|
||||
}, [path])
|
||||
// useEffect(() => {
|
||||
// console.log('🚀 ~ useEffect ~ objectNo:')
|
||||
// if (pathname === '/floor-plan') {
|
||||
|
||||
@ -21,8 +21,8 @@ import GlobalLoadingProvider from './GlobalLoadingProvider'
|
||||
* 서버 컴포넌트에 한해서 개별로 설정할 수 있음
|
||||
*/
|
||||
export const metadata = {
|
||||
title: 'HANASYS設計',
|
||||
description: 'HANASYS設計',
|
||||
title: 'HANASYS DESIGN',
|
||||
description: 'HANASYS DESIGN',
|
||||
}
|
||||
|
||||
/**
|
||||
@ -58,6 +58,7 @@ export default async function RootLayout({ children }) {
|
||||
pwdInitYn: session.pwdInitYn,
|
||||
custCd: session.custCd,
|
||||
isLoggedIn: session.isLoggedIn,
|
||||
builderNo: session.builderNo
|
||||
}
|
||||
}
|
||||
if (!headerPathname.includes('/login') && !session.isLoggedIn) {
|
||||
|
||||
@ -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: {
|
||||
@ -125,6 +126,11 @@ export const TRESTLE_MATERIAL = {
|
||||
BRACKET: 'bracket',
|
||||
}
|
||||
|
||||
export const MODULE_SETUP_TYPE = {
|
||||
LAYOUT: 'layout',
|
||||
AUTO: 'auto',
|
||||
}
|
||||
|
||||
export const SAVE_KEY = [
|
||||
'selectable',
|
||||
'name',
|
||||
@ -203,6 +209,17 @@ export const SAVE_KEY = [
|
||||
'fontWeight',
|
||||
'dormerAttributes',
|
||||
'toFixed',
|
||||
'startPoint',
|
||||
'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]
|
||||
|
||||
@ -14,6 +14,7 @@ import { sessionStore } from '@/store/commonAtom'
|
||||
import { isObjectNotEmpty } from '@/util/common-utils'
|
||||
|
||||
import BoardDetailModal from './community/modal/BoardDetailModal'
|
||||
import Config from '@/config/config.export'
|
||||
|
||||
export default function MainPage() {
|
||||
const [sessionState, setSessionState] = useRecoilState(sessionStore)
|
||||
|
||||
@ -2,26 +2,111 @@
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { setSession, login } from '@/lib/authActions'
|
||||
import { sessionStore } from '@/store/commonAtom'
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { useAxios } from '@/hooks/useAxios'
|
||||
import { globalLocaleStore } from '@/store/localeAtom'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
import GlobalSpinner from '@/components/common/spinner/GlobalSpinner'
|
||||
|
||||
export default function AutoLoginPage() {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
export default function AutoLoginPage({ autoLoginParam }) {
|
||||
const router = useRouter()
|
||||
|
||||
const [isLoading, setIsLoading] = useState(autoLoginParam === 'Y' ? false : true)
|
||||
const [globalLocaleState, setGlbalLocaleState] = useRecoilState(globalLocaleStore)
|
||||
|
||||
const { promisePost } = useAxios(globalLocaleState)
|
||||
const { getMessage } = useMessage()
|
||||
|
||||
const [userId, setUserId] = useState('')
|
||||
const [sessionState, setSessionState] = useRecoilState(sessionStore)
|
||||
|
||||
const [idFocus, setIdFocus] = useState(false)
|
||||
|
||||
const loginProcess = async () => {
|
||||
setIsLoading(true)
|
||||
await promisePost({ url: '/api/login/v1.0/user', data: { loginId: userId } }).then((response) => {
|
||||
setIsLoading(false)
|
||||
if (response.data) {
|
||||
const res = response.data
|
||||
const result = { ...res, storeLvl: res.groupId === '60000' ? '1' : '2', pwdInitYn: 'Y' }
|
||||
setSession(result)
|
||||
setSessionState(result)
|
||||
login()
|
||||
} else {
|
||||
alert(getMessage('login.fail'))
|
||||
router.push('/login?autoLoginParam1=Y')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading && <GlobalSpinner />}
|
||||
<div className="login-input-frame">
|
||||
<div className="login-frame-tit ">
|
||||
<span>{getMessage('site.name')}</span>
|
||||
{getMessage('site.sub_name')}
|
||||
</div>
|
||||
<div className="login-input-wrap">
|
||||
<div className="login-area id" style={{ fontWeight: 'bolder' }}>
|
||||
{getMessage('login.auto.page.text')}
|
||||
{autoLoginParam !== 'Y' ? (
|
||||
<>
|
||||
<div className="login-input-frame">
|
||||
<div className="login-frame-tit ">
|
||||
<span>{getMessage('site.name')}</span>
|
||||
{getMessage('site.sub_name')}
|
||||
</div>
|
||||
<div className="login-input-wrap">
|
||||
<div className="login-area id" style={{ fontWeight: 'bolder' }}>
|
||||
{getMessage('login.auto.page.text')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="login-input-frame">
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
loginProcess()
|
||||
}}
|
||||
className="space-y-6"
|
||||
>
|
||||
<div className="login-frame-tit">
|
||||
<span>{getMessage('site.name')}</span>
|
||||
{getMessage('site.sub_name')}
|
||||
</div>
|
||||
<div className="login-input-wrap">
|
||||
<div className={`login-area id ${idFocus ? 'focus' : ''}`}>
|
||||
<input
|
||||
type="text"
|
||||
className="login-input"
|
||||
id="userId"
|
||||
name="id"
|
||||
required
|
||||
value={userId}
|
||||
placeholder={getMessage('login.id.placeholder')}
|
||||
onChange={(e) => {
|
||||
setUserId(e.target.value)
|
||||
}}
|
||||
onFocus={() => setIdFocus(true)}
|
||||
onBlur={() => setIdFocus(false)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="id-delete"
|
||||
onClick={(e) => {
|
||||
setUserId('')
|
||||
}}
|
||||
></button>
|
||||
</div>
|
||||
<div className="login-btn-box">
|
||||
<button type="submit" className="login-btn">
|
||||
{getMessage('login')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -25,7 +25,9 @@ export default function Login() {
|
||||
|
||||
useEffect(() => {
|
||||
if (autoLoginParam) {
|
||||
autoLoginProcess(autoLoginParam)
|
||||
if (autoLoginParam !== 'Y') {
|
||||
autoLoginProcess(autoLoginParam)
|
||||
}
|
||||
}
|
||||
|
||||
// console.log('🚀 ~ checkSession ~ checkSession():', checkSession())
|
||||
@ -334,7 +336,7 @@ export default function Login() {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{autoLoginParam && <AutoLogin />}
|
||||
{autoLoginParam && <AutoLogin autoLoginParam={autoLoginParam} />}
|
||||
</div>
|
||||
<div className="login-copyright">COPYRIGHT©2024 Hanwha Japan All Rights Reserved.</div>
|
||||
</div>
|
||||
|
||||
@ -6,29 +6,19 @@ import { contextMenuListState, contextMenuState } from '@/store/contextMenu'
|
||||
import { useTempGrid } from '@/hooks/useTempGrid'
|
||||
import { useContextMenu } from '@/hooks/useContextMenu'
|
||||
import { useEvent } from '@/hooks/useEvent'
|
||||
import { canvasState } from '@/store/canvasAtom'
|
||||
import { canvasState, currentObjectState } from '@/store/canvasAtom'
|
||||
|
||||
export default function QContextMenu(props) {
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
const { contextRef, canvasProps } = props
|
||||
const [contextMenu, setContextMenu] = useRecoilState(contextMenuState)
|
||||
const contextMenuList = useRecoilValue(contextMenuListState)
|
||||
const activeObject = canvasProps?.getActiveObject() //액티브된 객체를 가져옴
|
||||
const currentObject = useRecoilValue(currentObjectState)
|
||||
const { tempGridMode, setTempGridMode } = useTempGrid()
|
||||
const { handleKeyup } = useContextMenu()
|
||||
const { addDocumentEventListener, removeDocumentEvent } = useEvent()
|
||||
// const { addDocumentEventListener, removeDocumentEvent } = useContext(EventContext)
|
||||
|
||||
let contextType = ''
|
||||
|
||||
if (activeObject) {
|
||||
if (activeObject.initOptions && activeObject.initOptions.name) {
|
||||
//이건 바뀔 가능성이 있음
|
||||
if (activeObject.initOptions?.name?.indexOf('guide') > -1) {
|
||||
contextType = 'surface' //면형상
|
||||
}
|
||||
}
|
||||
}
|
||||
const getYPosition = (e) => {
|
||||
const contextLength = contextMenuList.reduce((acc, cur, index) => {
|
||||
return acc + cur.length
|
||||
@ -36,11 +26,13 @@ export default function QContextMenu(props) {
|
||||
return e?.clientY - (contextLength * 25 + contextMenuList.length * 2 * 17)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!contextRef.current) return
|
||||
const handleContextMenu = (e) => {
|
||||
// e.preventDefault() //기존 contextmenu 막고
|
||||
|
||||
if (currentObject) {
|
||||
const isArray = currentObject.hasOwnProperty('arrayData')
|
||||
if (isArray && currentObject.arrayData.length === 0) return
|
||||
|
||||
const handleContextMenu = (e) => {
|
||||
e.preventDefault() //기존 contextmenu 막고
|
||||
if (tempGridMode) return
|
||||
const position = {
|
||||
x: window.innerWidth / 2 < e.pageX ? e.pageX - 240 : e.pageX,
|
||||
@ -48,21 +40,24 @@ export default function QContextMenu(props) {
|
||||
}
|
||||
setContextMenu({ visible: true, ...position, currentMousePos: canvasProps.getPointer(e) })
|
||||
addDocumentEventListener('keyup', document, handleKeyup)
|
||||
canvasProps?.upperCanvasEl.removeEventListener('contextmenu', handleContextMenu) //한번 노출 후 이벤트 삭제
|
||||
}
|
||||
}
|
||||
|
||||
const handleClick = (e) => {
|
||||
// e.preventDefault()
|
||||
const handleClick = (e) => {
|
||||
// e.preventDefault()
|
||||
setContextMenu({ ...contextMenu, visible: false })
|
||||
}
|
||||
|
||||
const handleOutsideClick = (e) => {
|
||||
// e.preventDefault()
|
||||
if (contextMenu.visible) {
|
||||
setContextMenu({ ...contextMenu, visible: false })
|
||||
removeDocumentEvent('keyup')
|
||||
}
|
||||
}
|
||||
|
||||
const handleOutsideClick = (e) => {
|
||||
// e.preventDefault()
|
||||
if (contextMenu.visible) {
|
||||
setContextMenu({ ...contextMenu, visible: false })
|
||||
removeDocumentEvent('keyup')
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!contextRef.current) return
|
||||
|
||||
canvasProps?.upperCanvasEl.addEventListener('contextmenu', handleContextMenu)
|
||||
document.addEventListener('click', handleClick)
|
||||
@ -72,43 +67,9 @@ export default function QContextMenu(props) {
|
||||
removeDocumentEvent('keyup')
|
||||
document.removeEventListener('click', handleClick)
|
||||
document.removeEventListener('click', handleOutsideClick)
|
||||
canvasProps?.upperCanvasEl.removeEventListener('contextmenu', handleContextMenu) //한번 노출 후 이벤트 삭제
|
||||
}
|
||||
}, [contextRef, contextMenuList])
|
||||
|
||||
const handleObjectMove = () => {
|
||||
activeObject.set({
|
||||
lockMovementX: false, // X 축 이동 잠금
|
||||
lockMovementY: false, // Y 축 이동 잠금
|
||||
})
|
||||
|
||||
canvasProps?.on('object:modified', function (e) {
|
||||
activeObject.set({
|
||||
lockMovementX: true, // X 축 이동 잠금
|
||||
lockMovementY: true, // Y 축 이동 잠금
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const handleObjectDelete = () => {
|
||||
if (confirm('삭제하실거?')) {
|
||||
canvasProps.remove(activeObject)
|
||||
}
|
||||
}
|
||||
|
||||
const handleObjectCopy = () => {
|
||||
activeObject.clone((cloned) => {
|
||||
cloned.set({
|
||||
left: activeObject.left + activeObject.width + 20,
|
||||
initOptions: { ...activeObject.initOptions },
|
||||
lockMovementX: true, // X 축 이동 잠금
|
||||
lockMovementY: true, // Y 축 이동 잠금
|
||||
lockRotation: true, // 회전 잠금
|
||||
lockScalingX: true, // X 축 크기 조정 잠금
|
||||
lockScalingY: true, // Y 축 크기 조정 잠금
|
||||
})
|
||||
canvasProps?.add(cloned)
|
||||
})
|
||||
}
|
||||
}, [contextRef, contextMenuList, currentObject])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -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}
|
||||
|
||||
442
src/components/common/input/CalcInput.jsx
Normal 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'
|
||||
@ -14,6 +14,7 @@ import { useMessage } from '@/hooks/useMessage'
|
||||
* @param {string} targetKey - value에 있는 키
|
||||
* @param {string} showKey - options 있는 키중 보여줄 키
|
||||
* @param {object} params - 추가 파라미터
|
||||
* @param {boolean} showFirstOptionWhenEmpty - value가 빈값일 때 첫 번째 옵션을 보여줄지 여부
|
||||
* @returns
|
||||
*/
|
||||
export default function QSelectBox({
|
||||
@ -26,6 +27,8 @@ export default function QSelectBox({
|
||||
targetKey = '',
|
||||
showKey = '',
|
||||
params = {},
|
||||
tagTitle = '',
|
||||
showFirstOptionWhenEmpty = false,
|
||||
}) {
|
||||
const { getMessage } = useMessage()
|
||||
|
||||
@ -38,8 +41,10 @@ export default function QSelectBox({
|
||||
if (options.length === 0) return title !== '' ? title : getMessage('selectbox.title')
|
||||
if (showKey !== '' && !value) {
|
||||
//value가 없으면 showKey가 있으면 우선 보여준다
|
||||
// return options[0][showKey]
|
||||
return title
|
||||
if (showFirstOptionWhenEmpty && options.length > 0) {
|
||||
return options[0][showKey]
|
||||
}
|
||||
return title !== '' ? title : getMessage('selectbox.title')
|
||||
} else if (showKey !== '' && value) {
|
||||
//value가 있으면 sourceKey와 targetKey를 비교하여 보여준다
|
||||
|
||||
@ -47,12 +52,18 @@ export default function QSelectBox({
|
||||
return option[sourceKey] === value[targetKey]
|
||||
})
|
||||
if (!option) {
|
||||
if (showFirstOptionWhenEmpty && options.length > 0) {
|
||||
return options[0][showKey]
|
||||
}
|
||||
return title !== '' ? title : getMessage('selectbox.title')
|
||||
} else {
|
||||
return option[showKey]
|
||||
}
|
||||
} else {
|
||||
//일치하는 조건이 없으면 기본값을 보여준다.
|
||||
if (showFirstOptionWhenEmpty && options.length > 0) {
|
||||
return showKey !== '' ? options[0][showKey] : options[0].name
|
||||
}
|
||||
return title !== '' ? title : getMessage('selectbox.title')
|
||||
}
|
||||
}
|
||||
@ -73,7 +84,7 @@ export default function QSelectBox({
|
||||
useEffect(() => {
|
||||
// value && handleClickSelectOption(value)
|
||||
setSelected(handleInitState())
|
||||
}, [options, value, sourceKey, targetKey, showKey])
|
||||
}, [options, value, sourceKey, targetKey, showKey, showFirstOptionWhenEmpty])
|
||||
|
||||
useOnClickOutside(ref, handleClose)
|
||||
|
||||
@ -82,12 +93,13 @@ export default function QSelectBox({
|
||||
className={`sort-select ${openSelect ? 'active' : ''} ${disabled ? 'disabled' : ''}`}
|
||||
ref={ref}
|
||||
onClick={disabled ? () => {} : () => setOpenSelect(!openSelect)}
|
||||
title={tagTitle}
|
||||
>
|
||||
<p>{selected}</p>
|
||||
<ul className="select-item-wrap">
|
||||
{options?.length > 0 &&
|
||||
options?.map((option, index) => (
|
||||
<li key={option.id || index} className="select-item" onClick={() => handleClickSelectOption(option)}>
|
||||
<li key={option.id + '_' + index} className="select-item" onClick={() => handleClickSelectOption(option)}>
|
||||
<button key={option.id + 'btn'}>{showKey !== '' ? option[showKey] : option.name}</button>
|
||||
</li>
|
||||
))}
|
||||
|
||||
212
src/components/community/Qna.jsx
Normal file
@ -0,0 +1,212 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
|
||||
import Search from '@/components/community/Search'
|
||||
import Pagination from '@/components/community/Pagination'
|
||||
|
||||
import { useContext } from 'react'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useResetRecoilState, useRecoilValue, useRecoilState } from 'recoil'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
|
||||
import { searchState } from '@/store/boardAtom'
|
||||
|
||||
import { QcastContext } from '@/app/QcastProvider'
|
||||
import QnaBoardDetailModal from '@/components/community/modal/QnaDetailModal'
|
||||
import { sessionStore } from '@/store/commonAtom'
|
||||
import { useAxios } from '@/hooks/useAxios'
|
||||
import { useCommonCode } from '@/hooks/common/useCommonCode'
|
||||
|
||||
export default function Qna() {
|
||||
const { getMessage } = useMessage()
|
||||
const resetSearch = useResetRecoilState(searchState)
|
||||
const [isInitialized, setIsInitialized] = useState(false)
|
||||
|
||||
//const search = useRecoilValue(searchState)
|
||||
const [searchForm, setSearchForm] = useRecoilState(searchState)
|
||||
const { findCommonCode } = useCommonCode()
|
||||
const { setIsGlobalLoading } = useContext(QcastContext)
|
||||
const { get } = useAxios()
|
||||
const [boardList, setBoardList] = useState([])
|
||||
const [sessionState, setSessionState] = useRecoilState(sessionStore)
|
||||
const [search, setSearch] = useRecoilState(searchState)
|
||||
// 팝업 관련
|
||||
const [open, setOpen] = useState(false)
|
||||
const [modalQnaNo, setModalQnaNo] = useState('')
|
||||
const [modalQnaType, setModalQnaType] = useState('')
|
||||
|
||||
// 목록 조회
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
setIsGlobalLoading(true)
|
||||
const startRow = (search.currentPage - 1) * search.pageBlock > 0 ? (search.currentPage - 1) * search.pageBlock + 1 : 1
|
||||
const endRow = search.currentPage * search.pageBlock
|
||||
|
||||
const url = `/api/board/list`
|
||||
const params = new URLSearchParams({
|
||||
schNoticeTpCd : 'QC',
|
||||
schNoticeClsCd: 'QNA',
|
||||
compCd : 5200,
|
||||
storeId : sessionState.storeId,
|
||||
loginId : sessionState.userId,
|
||||
schTitle : search.searchValue ? search.searchValue : '',
|
||||
startRow : startRow,
|
||||
endRow : endRow,
|
||||
schMainYn : 'N',
|
||||
siteTpCd : 'QC',
|
||||
})
|
||||
const apiUrl = `${url}?${params.toString()}`
|
||||
|
||||
const resultData = await get({ url: apiUrl })
|
||||
|
||||
if (resultData) {
|
||||
if (resultData.result.code === 200) {
|
||||
if (resultData.data.length > 0) {
|
||||
setBoardList(resultData.data)
|
||||
setSearch({ ...search, totalCount: resultData.data[0].totCnt })
|
||||
} else {
|
||||
setBoardList([])
|
||||
setSearch({ ...search, totalCount: 0 })
|
||||
}
|
||||
} else {
|
||||
alert(resultData.result.message)
|
||||
}
|
||||
}
|
||||
setIsGlobalLoading(false)
|
||||
}
|
||||
|
||||
fetchData()
|
||||
}, [search.currentPage, search.searchValue, search.pageBlock, search.searchFlag])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (search.mainFlag === 'N') {
|
||||
resetSearch()
|
||||
} else {
|
||||
//메인에서 FAQ 조회 왔을때 로딩바 해제
|
||||
setIsGlobalLoading(false)
|
||||
setSearchForm({ ...searchForm, mainFlag: 'N' })
|
||||
}
|
||||
setIsInitialized(true)
|
||||
|
||||
//문의구분코드
|
||||
// const codeL = findCommonCode(204200)
|
||||
// const codeM = findCommonCode(204300)
|
||||
// const codeS = findCommonCode(204400)
|
||||
|
||||
|
||||
|
||||
}, [])
|
||||
|
||||
if (!isInitialized) {
|
||||
return null
|
||||
}
|
||||
|
||||
const boardType = {
|
||||
boardTitle: getMessage('qna.title'),
|
||||
subTitle: getMessage('qna.sub.title'),
|
||||
clsCode: 'QNA',
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="sub-header">
|
||||
<div className="sub-header-inner">
|
||||
<ul className="sub-header-title-wrap">
|
||||
<li className="title-item">
|
||||
<Link className="sub-header-title" href={'#'}>
|
||||
{getMessage('qna.title')}
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<ul className="sub-header-location">
|
||||
<li className="location-item">
|
||||
<span className="home">
|
||||
<Image src="/static/images/main/home_icon.svg" alt="react" width={16} height={16} />
|
||||
</span>
|
||||
</li>
|
||||
<li className="location-item">
|
||||
<span>{getMessage('header.menus.community')}</span>
|
||||
</li>
|
||||
<li className="location-item">
|
||||
<span>{getMessage('qna.title')}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sub-content">
|
||||
<div className="sub-content-inner">
|
||||
<div className="sub-table-box">
|
||||
<Search title={boardType.boardTitle} subTitle={boardType.subTitle} isSelectUse={true} clsCode={boardType.clsCode} />
|
||||
{/*<QnaTable clsCode={boardType.clsCode} />*/}
|
||||
<div className="community-table">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col width={100}/>
|
||||
<col width={150}/>
|
||||
<col />
|
||||
<col width={150}/>
|
||||
<col width={150}/>
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{boardList.length > 0 ? (
|
||||
boardList?.map((board) => (
|
||||
<tr
|
||||
key={board.qnaNo}
|
||||
onClick={() => {
|
||||
setOpen(true)
|
||||
setModalQnaNo(board.qnaNo)
|
||||
setModalQnaType("["+board?.qnaClsLrgCd+"/"+board?.qnaClsMidCd+"/"+board?.qnaClsSmlCd+"]")
|
||||
}}
|
||||
>
|
||||
<td className="al-c">
|
||||
{/* 번호 */}
|
||||
{board.totCnt - board.rowNumber + 1}
|
||||
</td>
|
||||
{/* 답변 */}
|
||||
{board?.answerYn === 'Y'? (<td className="al-c "> {getMessage('qna.list.header.answer.yes')}</td>) : (<td className="al-c org"> {getMessage('qna.list.header.answer.no')}</td>)}
|
||||
<td>
|
||||
<div className="mb5">[{board?.qnaClsLrgCd} / {board?.qnaClsMidCd} / {board?.qnaClsSmlCd}]</div>
|
||||
{/* 제목 */}
|
||||
<div className="text-frame">
|
||||
<div className="text-overflow">{board.title}{board.qstTitle}</div>
|
||||
{board.attachYn === 'Y' && <span className="clip"></span>}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="renewal">
|
||||
{/*{board.uptDt && (*/}
|
||||
{/* <>*/}
|
||||
{/* (<span>{getMessage('board.uptDt')}</span> : {board.uptDt})*/}
|
||||
{/* </>*/}
|
||||
{/*)}*/}
|
||||
{board.regUserNm}
|
||||
</div>
|
||||
</td>
|
||||
<td className="al-c">
|
||||
{/* 등록일 */}
|
||||
{board.regDt.split(' ')[0]}
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td className="al-c no-data" colSpan={5}>{getMessage('common.message.no.data')}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<Pagination />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{open && <QnaBoardDetailModal qnaNo={modalQnaNo} setOpen={setOpen} qnaType = {modalQnaType} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -4,10 +4,11 @@ import { searchState } from '@/store/boardAtom'
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
import { useState } from 'react'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import QnaRegModal from '@/components/community/modal/QnaRegModal'
|
||||
|
||||
export default function Search({ title = '', subTitle = '', isSelectUse = false }) {
|
||||
export default function Search({ title = '', subTitle = '', isSelectUse = false, clsCode = '' }) {
|
||||
const { getMessage } = useMessage()
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
const search = useRecoilValue(searchState)
|
||||
const [searchForm, setSearchForm] = useRecoilState(searchState)
|
||||
|
||||
@ -32,7 +33,13 @@ export default function Search({ title = '', subTitle = '', isSelectUse = false
|
||||
} else {
|
||||
setSearchView(false)
|
||||
setSearchViewText('')
|
||||
setSearchForm({ ...searchForm, currentPage: 1, searchValue: '', pageBlock: block, searchFlag: !searchForm.searchFlag })
|
||||
setSearchForm({
|
||||
...searchForm,
|
||||
currentPage: 1,
|
||||
searchValue: '',
|
||||
pageBlock : block,
|
||||
searchFlag : !searchForm.searchFlag,
|
||||
})
|
||||
}
|
||||
// 조회 후 값 비워주기
|
||||
setSearchValue('')
|
||||
@ -57,7 +64,10 @@ export default function Search({ title = '', subTitle = '', isSelectUse = false
|
||||
onKeyDown={handleKeyDown}
|
||||
value={searchValue}
|
||||
/>
|
||||
<button type="button" className="community-search-ico" onClick={() => handleSearch(searchValue, selectPageBlock)}></button>
|
||||
|
||||
<button type="button" className="community-search-ico"
|
||||
onClick={() => handleSearch(searchValue, selectPageBlock)}></button>
|
||||
|
||||
</div>
|
||||
{searchView && (
|
||||
<div className="community-search-keyword">
|
||||
@ -92,6 +102,14 @@ export default function Search({ title = '', subTitle = '', isSelectUse = false
|
||||
</div>
|
||||
{isSelectUse && (
|
||||
<div className="left-unit-box">
|
||||
{clsCode === 'QNA' &&
|
||||
<div>
|
||||
<button className="btn-origin navy mr10"
|
||||
onClick={() => {
|
||||
setOpen(true)
|
||||
}}> {getMessage('qna.sub.btn.inquiry')}</button>
|
||||
</div>
|
||||
}
|
||||
<div className="select-box" style={{ width: '80px' }}>
|
||||
<select
|
||||
className="select-light black"
|
||||
@ -112,6 +130,7 @@ export default function Search({ title = '', subTitle = '', isSelectUse = false
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{open && <QnaRegModal setOpen={setOpen} setReload={handleSearch} searchValue={searchValue ? searchValue : searchViewText} selectPageBlock = {selectPageBlock}/>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
128
src/components/community/modal/QnaDetailModal.jsx
Normal file
@ -0,0 +1,128 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useAxios } from '@/hooks/useAxios'
|
||||
import { handleFileDown } from '@/util/board-utils'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { sessionStore } from '@/store/commonAtom'
|
||||
import { useRecoilState } from 'recoil'
|
||||
|
||||
export default function QnaDetailModal({ qnaNo, setOpen, qnaType }) {
|
||||
const { getMessage } = useMessage()
|
||||
// api 조회 관련
|
||||
const { get } = useAxios()
|
||||
const [boardDetail, setBoardDetail] = useState({})
|
||||
const [sessionState, setSessionState] = useRecoilState(sessionStore)
|
||||
|
||||
useEffect(() => {
|
||||
// 상세 조회
|
||||
const fetchDetail = async (qnaNo) => {
|
||||
const url = `/api/board/detail`
|
||||
const params = new URLSearchParams({
|
||||
noticeNo : qnaNo,
|
||||
qnaNo : qnaNo,
|
||||
schNoticeClsCd: 'QNA',
|
||||
compCd : 5200,
|
||||
loginId : sessionState.userId,
|
||||
langCd : 'JA',
|
||||
siteTpCd : 'QC',
|
||||
})
|
||||
const apiUrl = `${url}?${params.toString()}`
|
||||
|
||||
const resultData = await get({ url: apiUrl })
|
||||
|
||||
if (resultData) {
|
||||
if (resultData.result.code === 200) {
|
||||
const boardDetail = resultData.data
|
||||
setBoardDetail(boardDetail)
|
||||
} else {
|
||||
alert(resultData.result.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fetchDetail(qnaNo)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div key={qnaNo} className="modal-popup community">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<button
|
||||
type="button"
|
||||
className="modal-close"
|
||||
onClick={() => {
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
{getMessage('board.sub.btn.close')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="oneonone-header-wrap">
|
||||
<div className="oneonone-title"> {qnaType} {boardDetail.qstTitle}</div>
|
||||
<div className="oneonone-infor">
|
||||
<div className="profile">{boardDetail.regUserNm}</div>
|
||||
<div className="date">{boardDetail.regDt}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="oneonone-detail">
|
||||
{boardDetail.listFile && (
|
||||
<dl className="community_detail-file-wrap">
|
||||
<dt>{getMessage('qna.detail.sub.fileList')}</dt>
|
||||
{boardDetail.listFile.map((boardFile) => (
|
||||
<dd key={boardFile.encodeFileNo}>
|
||||
<button type="button" className="down" onClick={() => handleFileDown(boardFile.fileNo, 'NO')}>
|
||||
{boardFile.srcFileNm}
|
||||
</button>
|
||||
</dd>
|
||||
))}
|
||||
</dl>
|
||||
)}
|
||||
<div
|
||||
className="community_detail-inner"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: boardDetail.qstContents ? boardDetail.qstContents.replaceAll('\n', '<br/>') : '',
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
{boardDetail?.answerYn === 'Y' && (
|
||||
<div className="oneonone-answer">
|
||||
<div className="answer-title-wrap">
|
||||
<div className="answer-title">Hanwha Japan {getMessage('qna.detail.sub.answer')}</div>
|
||||
<div className="oneonone-infor">
|
||||
<div className="profile">{boardDetail.ansRegNm}</div>
|
||||
<div className="date">{boardDetail.ansRegDt}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="community_detail-inner"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: boardDetail.ansContents ? boardDetail.ansContents.replaceAll('\n', '<br/>') : '',
|
||||
}}
|
||||
></div>
|
||||
{boardDetail.ansListFile && (
|
||||
<dl className="community_detail-file-wrap">
|
||||
<dt>{getMessage('qna.detail.sub.fileList')}</dt>
|
||||
{boardDetail.ansListFile.map((boardFile) => (
|
||||
<dd key={boardFile.encodeFileNo}>
|
||||
<button type="button" className="down" onClick={() => handleFileDown(boardFile.fileNo, 'N')}>
|
||||
{boardFile.srcFileNm}
|
||||
</button>
|
||||
</dd>
|
||||
))}
|
||||
</dl>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
140
src/components/community/modal/QnaFileUploader.jsx
Normal file
@ -0,0 +1,140 @@
|
||||
'use client'
|
||||
|
||||
import { useRef } from 'react'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { useSwal } from '@/hooks/useSwal'
|
||||
|
||||
export default function QnaFileUploader({ uploadFiles, setUploadFiles, qnaData, setQnaData }) {
|
||||
const fileInputRef = useRef(null)
|
||||
const { getMessage } = useMessage()
|
||||
|
||||
const { swalFire } = useSwal()
|
||||
|
||||
const handleButtonClick = (e) => {
|
||||
e.preventDefault()
|
||||
fileInputRef.current.click()
|
||||
}
|
||||
|
||||
const onChangeFiles = async (e) => {
|
||||
if (e.target.files.length <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const fileList = []
|
||||
let passFlag = true
|
||||
const allowedFileTypes = [
|
||||
'image/',
|
||||
'application/pdf',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation', // PPTX 형식
|
||||
'application/vnd.ms-powerpoint', // PPT 형식
|
||||
]
|
||||
Array.from(e.target.files).forEach((file) => {
|
||||
//엑셀, pdf, 이미지
|
||||
const fileType = file.type
|
||||
if (!allowedFileTypes.some((type) => fileType.includes(type))) {
|
||||
passFlag = false
|
||||
} else {
|
||||
fileList.push({ data: file, id: uuidv4() })
|
||||
}
|
||||
})
|
||||
|
||||
if (!passFlag) {
|
||||
swalFire({ text: getMessage('estimate.detail.fileList.extCheck'), type: 'alert', icon: 'error' })
|
||||
}
|
||||
|
||||
setUploadFiles([...uploadFiles, ...fileList])
|
||||
setQnaData({...qnaData, files:[...uploadFiles, ...fileList]})
|
||||
e.target.value = ''
|
||||
}
|
||||
|
||||
const deleteFile = (id) => {
|
||||
setUploadFiles(uploadFiles.filter((file) => file.id !== id))
|
||||
setQnaData({...qnaData, files:uploadFiles.filter((file) => file.id !== id)})
|
||||
}
|
||||
|
||||
const handleDrop = (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const fileList = []
|
||||
let passFlag = true
|
||||
const allowedFileTypes = [
|
||||
'image/',
|
||||
'application/pdf',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation', // PPTX 형식
|
||||
'application/vnd.ms-powerpoint', // PPT 형식
|
||||
]
|
||||
|
||||
Array.from(e.dataTransfer.files).forEach((file) => {
|
||||
//엑셀, pdf, 이미지
|
||||
let fileType = file.type
|
||||
if (!allowedFileTypes.some((type) => fileType.includes(type))) {
|
||||
passFlag = false
|
||||
} else {
|
||||
fileList.push({ data: file, id: uuidv4() })
|
||||
}
|
||||
})
|
||||
|
||||
if (!passFlag) {
|
||||
swalFire({ text: getMessage('estimate.detail.fileList.extCheck'), type: 'alert', icon: 'error' })
|
||||
}
|
||||
|
||||
setUploadFiles([...uploadFiles, ...fileList])
|
||||
setQnaData({...qnaData, files:[...uploadFiles, ...fileList]})
|
||||
}
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
const handleDragEnd = (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
const handleDragLeave = (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<div className="design-request-grid mt15">
|
||||
<div className="design-request-count">
|
||||
<div className="design-request-grid-tit">{getMessage("qna.reg.header.fileList")}</div>
|
||||
<div className="btn-area one-on-one">
|
||||
<label className="file-upload" htmlFor="img" onClick={handleButtonClick}>
|
||||
Attach File
|
||||
</label>
|
||||
<input type="file" multiple name="file" ref={fileInputRef} style={{ display: 'none' }} onChange={(e) => onChangeFiles(e)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="drag-file-box one-on-one">
|
||||
<div className="drag-file-area"
|
||||
draggable
|
||||
onDrop={(e) => handleDrop(e)}
|
||||
onDragOver={(e) => handleDragOver(e)}
|
||||
onDragEnd={(e) => handleDragEnd(e)}
|
||||
onDragLeave={(e) => handleDragLeave(e)}>
|
||||
<p>Drag file here</p>
|
||||
<ul className="file-list">
|
||||
{uploadFiles.length > 0 &&
|
||||
uploadFiles.map((file) => (
|
||||
<li className="file-item" key={file.id}>
|
||||
<span>
|
||||
{file.data.name} <button className="delete" onClick={() => deleteFile(file.id)}></button>
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
462
src/components/community/modal/QnaRegModal.jsx
Normal file
@ -0,0 +1,462 @@
|
||||
'use client'
|
||||
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { sessionStore } from '@/store/commonAtom'
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
import QnaFileUploader from '@/components/community/modal/QnaFileUploader'
|
||||
import { useContext, useEffect, useRef, useState } from 'react'
|
||||
import { useCommonCode } from '@/hooks/common/useCommonCode'
|
||||
import Select from 'react-select'
|
||||
import dayjs from 'dayjs'
|
||||
import { useSwal } from '@/hooks/useSwal'
|
||||
import { QcastContext } from '@/app/QcastProvider'
|
||||
import { useAxios } from '@/hooks/useAxios'
|
||||
import { globalLocaleStore } from '@/store/localeAtom'
|
||||
import { e } from 'mathjs'
|
||||
import { set } from 'react-hook-form'
|
||||
|
||||
|
||||
export default function QnaRegModal({ setOpen, setReload, searchValue, selectPageBlock }) {
|
||||
const { getMessage } = useMessage()
|
||||
const [fileList, setFileList] = useState([])
|
||||
const [sessionState, setSessionState] = useRecoilState(sessionStore)
|
||||
const globalLocaleState = useRecoilValue(globalLocaleStore)
|
||||
const [files, setFiles] = useState([])
|
||||
//const [qnaData, setQnaData] = useState([])
|
||||
const [qnaData, setQnaData] = useState({})
|
||||
const [closeMdFlg, setCloseMdFlg] = useState(true)
|
||||
const [closeSmFlg, setCloseSmFlg] = useState(true)
|
||||
const [hideSmFlg, setHideSmFlg] = useState(false)
|
||||
const qnaTypeLgCodeRef = useRef(null)
|
||||
const qnaTypeMdCodeRef = useRef(null)
|
||||
const qnaTypeSmCodeRef = useRef(null)
|
||||
const qstMail = useRef(null);
|
||||
const regUserNmRef = useRef(null)
|
||||
const regUserTelNoRef = useRef(null)
|
||||
const titleRef = useRef(null)
|
||||
const contentsRef = useRef(null)
|
||||
const { findCommonCode } = useCommonCode()
|
||||
const [qnaTypeLgCodeList, setQnaTypeLgCodeList] = useState([])
|
||||
const [qnaTypeMdCodeList, setQnaTypeMdCodeList] = useState([])
|
||||
const [qnaTypeSmCodeList, setQnaTypeSmCodeList] = useState([])
|
||||
const [phoneNumber, setPhoneNumber] = useState("");
|
||||
const { swalFire } = useSwal()
|
||||
const { setIsGlobalLoading } = useContext(QcastContext)
|
||||
const [isBtnDisable, setIsBtnDisable] = useState(false);
|
||||
const { promiseGet, post, promisePost } = useAxios(globalLocaleState)
|
||||
|
||||
useEffect(() => {
|
||||
console.log('qnaData updated:', qnaData);
|
||||
}, [qnaData]);
|
||||
|
||||
let fileCheck = false;
|
||||
const regPhoneNumber = (e) => {
|
||||
const result = e.target.value
|
||||
.replace(/[^0-9.]/g, "")
|
||||
//.replace(/^(\d{0,3})(\d{0,4})(\d{0,4})$/g, "$1-$2-$3")
|
||||
//.replace(/(-{1,2})$/g, "");
|
||||
//setPhoneNumber(result);
|
||||
|
||||
setQnaData({...qnaData, regUserTelNo: result })
|
||||
}
|
||||
|
||||
const fileUploadProps = {
|
||||
uploadFiles: files,
|
||||
setUploadFiles: setFiles,
|
||||
|
||||
}
|
||||
|
||||
// const fileSave = (qnaData, fileUploadProps) => {
|
||||
// return qnaData.files.push(fileUploadProps.uploadFiles)
|
||||
// }
|
||||
|
||||
const initQnaReg = async () => {
|
||||
|
||||
|
||||
qstMail.current.value = ''
|
||||
regUserNmRef.current.value = ''
|
||||
regUserTelNoRef.current.value = ''
|
||||
qnaTypeLgCodeRef.current.setValue();
|
||||
qnaTypeMdCodeRef.current.setValue();
|
||||
qnaTypeSmCodeRef.current?.setValue();
|
||||
titleRef.current.value = ''
|
||||
contentsRef.current.value = ''
|
||||
|
||||
//setQnaData([])
|
||||
|
||||
setQnaData({
|
||||
compCd: "5200",
|
||||
siteTpCd: "QC",
|
||||
schNoticeClsCd: "QNA",
|
||||
regId: sessionState?.userId || '',
|
||||
storeId: sessionState?.userId || '',
|
||||
qstMail: sessionState?.email || '',
|
||||
qnaClsLrgCd: '',
|
||||
qnaClsMidCd: '',
|
||||
qnaClsSmlCd: ''
|
||||
});
|
||||
|
||||
const codeL = findCommonCode(204200)
|
||||
if (codeL != null) {
|
||||
setQnaTypeLgCodeList(codeL)
|
||||
}
|
||||
setIsGlobalLoading(false)
|
||||
setIsBtnDisable(false);
|
||||
}
|
||||
const onChangeQnaTypeL = (e) => {
|
||||
if(e === undefined || e === null) return;
|
||||
const codeM = findCommonCode(204300)
|
||||
if (codeM != null) {
|
||||
|
||||
let codeList = []
|
||||
|
||||
codeM.map((item) => {
|
||||
|
||||
if(item.clRefChr1 === e.clCode) {
|
||||
codeList.push(item);
|
||||
|
||||
}
|
||||
})
|
||||
setQnaTypeMdCodeList(codeList)
|
||||
setQnaData({ ...qnaData, qnaClsLrgCd:e.clCode})
|
||||
setCloseMdFlg(false)
|
||||
qnaTypeMdCodeRef.current.setValue();
|
||||
qnaTypeSmCodeRef.current?.setValue();
|
||||
}
|
||||
|
||||
}
|
||||
const onChangeQnaTypeM = (e) => {
|
||||
if (!e?.clCode) return;
|
||||
|
||||
// 중분류 코드 업데이트
|
||||
setQnaData(prevState => ({
|
||||
...prevState,
|
||||
qnaClsMidCd: e.clCode,
|
||||
// 소분류는 초기화 (새로 선택하도록)
|
||||
qnaClsSmlCd: ''
|
||||
}));
|
||||
|
||||
// 소분류 코드 목록 설정
|
||||
const codeS = findCommonCode(204400);
|
||||
if (codeS) {
|
||||
const filteredCodeList = codeS.filter(item => item.clRefChr1 === e.clCode);
|
||||
setQnaTypeSmCodeList(filteredCodeList);
|
||||
|
||||
// 소분류가 있으면 초기화, 없으면 숨김
|
||||
const hasSubCategories = filteredCodeList.length > 0;
|
||||
setCloseSmFlg(!hasSubCategories);
|
||||
setHideSmFlg(!hasSubCategories);
|
||||
} else {
|
||||
setHideSmFlg(true)
|
||||
}
|
||||
|
||||
// 소분류 선택기 초기화
|
||||
qnaTypeSmCodeRef.current?.setValue();
|
||||
};
|
||||
|
||||
|
||||
const onChangeQnaTypeS = (e) => {
|
||||
if (!e?.clCode) return;
|
||||
|
||||
setQnaData(prevState => ({
|
||||
...prevState,
|
||||
qnaClsSmlCd: e.clCode
|
||||
}));
|
||||
}
|
||||
|
||||
const onFileSave = () => {
|
||||
const formData= []
|
||||
if(fileUploadProps.uploadFiles.length === 0) return;
|
||||
if(!fileCheck) return;
|
||||
|
||||
fileUploadProps.uploadFiles.forEach((file) => {
|
||||
//console.log("file::::::::",file)
|
||||
formData.push(file)
|
||||
|
||||
})
|
||||
setQnaData({ ...qnaData, files:formData })
|
||||
fileCheck = false;
|
||||
}
|
||||
|
||||
const isValidEmail = (email) => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
};
|
||||
|
||||
const isEmpty = (value) => {
|
||||
return value === null || value === undefined || value.trim() === "";
|
||||
};
|
||||
|
||||
|
||||
const handleQnaSubmit = async () => {
|
||||
//필수 체크
|
||||
|
||||
//console.log("1::::",qnaData)
|
||||
|
||||
|
||||
let regUserNm = qnaData?.regUserNm??'';
|
||||
|
||||
if (!isValidEmail(qnaData.qstMail)) {
|
||||
qstMail.current.focus();
|
||||
swalFire({
|
||||
title: getMessage('qna.reg.alert.require.qstMail'),
|
||||
icon: 'warning',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (isEmpty(regUserNm)) {
|
||||
regUserNmRef.current.value = '';
|
||||
regUserNmRef.current.focus()
|
||||
swalFire({
|
||||
title: getMessage('qna.reg.alert.require.regUserNm'),
|
||||
icon: 'warning',
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
let qnaClsLrgCd = qnaData?.qnaClsLrgCd??'';
|
||||
let qnaClsMidCd = qnaData?.qnaClsMidCd??'';
|
||||
|
||||
if (isEmpty(qnaClsLrgCd) || isEmpty(qnaClsMidCd) ) {
|
||||
(isEmpty(qnaClsLrgCd))?qnaTypeLgCodeRef.current.focus():qnaTypeMdCodeRef.current.focus()
|
||||
swalFire({
|
||||
title: getMessage('qna.reg.alert.select.type'),
|
||||
icon: 'warning',
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
let title = qnaData?.title??'';
|
||||
|
||||
if (isEmpty(title)) {
|
||||
titleRef.current.value = '';
|
||||
titleRef.current.focus()
|
||||
swalFire({
|
||||
title: getMessage('qna.reg.alert.require.title'),
|
||||
icon: 'warning',
|
||||
})
|
||||
return false
|
||||
}
|
||||
//console.log("5::::",qnaData)
|
||||
let contents = qnaData?.contents??'';
|
||||
|
||||
if (isEmpty(contents)) {
|
||||
contentsRef.current.value = '';
|
||||
contentsRef.current.focus()
|
||||
swalFire({
|
||||
title: getMessage('qna.reg.alert.require.contents'),
|
||||
icon: 'warning',
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
const formData = new FormData()
|
||||
if(qnaData?.files?.length > 0) {
|
||||
qnaData?.files.forEach((file) => {
|
||||
formData.append('files', file.data)
|
||||
})
|
||||
}
|
||||
formData.append("compCd", qnaData.compCd)
|
||||
formData.append("siteTpCd", qnaData.siteTpCd)
|
||||
formData.append("qnaClsLrgCd", qnaData.qnaClsLrgCd)
|
||||
formData.append("qnaClsMidCd", qnaData.qnaClsMidCd)
|
||||
formData.append("qnaClsSmlCd", qnaData.qnaClsSmlCd)
|
||||
formData.append("title", qnaData.title)
|
||||
formData.append("contents", qnaData.contents)
|
||||
formData.append("regId", qnaData.regId)
|
||||
formData.append("storeId", qnaData.storeId)
|
||||
formData.append("regUserNm", qnaData.regUserNm)
|
||||
formData.append("regUserTelNo", qnaData.regUserTelNo)
|
||||
formData.append("qstMail", qnaData.qstMail)
|
||||
formData.append("schNoticeClsCd", qnaData.schNoticeClsCd)
|
||||
|
||||
|
||||
|
||||
//console.log(Array.from(formData));
|
||||
|
||||
swalFire({
|
||||
html: getMessage('qna.reg.confirm.save'),
|
||||
type: 'confirm',
|
||||
confirmFn: async () => {
|
||||
|
||||
setIsBtnDisable(true);
|
||||
setIsGlobalLoading(true)
|
||||
|
||||
try {
|
||||
|
||||
const apiUrl = 'api/board'
|
||||
//console.log("7::::",qnaData)
|
||||
await post({ url: `${apiUrl}/saveQna`, data: formData }).then((res) => {
|
||||
if (res?.result.code === 200) {
|
||||
//qnaData.newFileList = []
|
||||
setIsGlobalLoading(false)
|
||||
swalFire({ text: getMessage('qna.reg.alert.save'), type: 'alert' })
|
||||
setOpen(false)
|
||||
setReload(searchValue, selectPageBlock);
|
||||
}else{
|
||||
setIsGlobalLoading(false)
|
||||
swalFire({ text: getMessage('qna.reg.alert.saveFail'), type: 'alert', icon: 'error' })
|
||||
console.error('error::::::::::::', res)
|
||||
}
|
||||
|
||||
setIsBtnDisable(false)
|
||||
})
|
||||
} catch (e) {
|
||||
setIsGlobalLoading(false)
|
||||
setIsBtnDisable(false);
|
||||
console.error('error::::::::::::', e.message)
|
||||
swalFire({ text: e.message, type: 'alert' , icon: 'error'})
|
||||
console.error('error::::::::::::', e.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
initQnaReg()
|
||||
},[])
|
||||
|
||||
// useEffect(() => {
|
||||
// onFileSave()
|
||||
//
|
||||
// }, [onFileSave])
|
||||
return (
|
||||
<div className="modal-popup">
|
||||
<div className="modal-dialog big">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h1 className="title">{getMessage('qna.title')}</h1>
|
||||
<button className="modal-close"
|
||||
onClick={() => {
|
||||
setOpen(false)
|
||||
}}>{getMessage('board.sub.btn.close')}</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="modal-body-inner">
|
||||
<div className="design-request-table">
|
||||
<div className="common-table">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col style={{ width: '100px' }} />
|
||||
<col />
|
||||
<col style={{ width: '120px' }} />
|
||||
<col />
|
||||
<col style={{ width: '150px' }} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{getMessage('qna.list.header.regNm')}</th>
|
||||
<td><input type="text" className="input-light" value={sessionState?.userNm || ''} readOnly /></td>
|
||||
<th>E-Mail<span className="red">*</span></th>
|
||||
<td ><input type="text" className="input-light" required
|
||||
ref={qstMail}
|
||||
value={qnaData?.qstMail || ''}
|
||||
onChange={(e) => setQnaData({...qnaData, qstMail: e.target.value })}
|
||||
onBlur={(e) => setQnaData({ ...qnaData, qstMail: e.target.value })} />
|
||||
</td>
|
||||
|
||||
<th>{getMessage('qna.reg.header.regDt')}</th>
|
||||
<td>{dayjs(new Date()).format('YYYY-MM-DD')}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{getMessage('qna.reg.header.regUserNm')}<span className="red">*</span></th>
|
||||
<td ><input type="text" className="input-light" required
|
||||
ref={regUserNmRef}
|
||||
value={qnaData?.regUserNm || '' }
|
||||
onChange={(e) => setQnaData({...qnaData, regUserNm: e.target.value })}
|
||||
onBlur={(e) => setQnaData({ ...qnaData, regUserNm: e.target.value })} /> </td>
|
||||
<th>{getMessage('qna.reg.header.regUserTelNo')}</th>
|
||||
<td colSpan={3}><input type="text" className="input-light"
|
||||
ref={regUserTelNoRef}
|
||||
maxLength={13}
|
||||
value={qnaData?.regUserTelNo || '' }
|
||||
onChange={regPhoneNumber}
|
||||
/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="design-request-grid">
|
||||
<div className="design-request-count">
|
||||
<div className="design-request-grid-tit">{getMessage("qna.reg.header.type")}, {getMessage("qna.reg.header.title")} <span
|
||||
className="red">*</span></div>
|
||||
</div>
|
||||
<div className="flx-box one-on-one">
|
||||
<div className="select-wrap mr5" >
|
||||
<Select name="" ref={qnaTypeLgCodeRef}
|
||||
options={qnaTypeLgCodeList}
|
||||
placeholder="Select"
|
||||
onChange={(e) => onChangeQnaTypeL(e)}
|
||||
getOptionLabel={(x) => x.clCodeNm}
|
||||
getOptionValue={(x) => x.clCode}
|
||||
isClearable={false}
|
||||
isSearchable={false}
|
||||
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div className="select-wrap mr5" >
|
||||
<Select name="" ref={qnaTypeMdCodeRef}
|
||||
options={qnaTypeMdCodeList}
|
||||
placeholder="Select"
|
||||
onChange={(e) => onChangeQnaTypeM(e)}
|
||||
getOptionLabel={(x) => x.clCodeNm}
|
||||
getOptionValue={(x) => x.clCode}
|
||||
isClearable={false}
|
||||
isSearchable={false}
|
||||
isDisabled={closeMdFlg}
|
||||
defaultValue={''}
|
||||
/>
|
||||
</div>
|
||||
<div className="select-wrap" >
|
||||
{!hideSmFlg && (
|
||||
<Select name="" ref={qnaTypeSmCodeRef}
|
||||
options={qnaTypeSmCodeList}
|
||||
placeholder="Select"
|
||||
onChange={(e) => onChangeQnaTypeS(e)}
|
||||
getOptionLabel={(x) => x.clCodeNm}
|
||||
getOptionValue={(x) => x.clCode}
|
||||
isClearable={false}
|
||||
isSearchable={false}
|
||||
isDisabled={closeSmFlg}
|
||||
/>)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="input-wrap mt5">
|
||||
<input type="text" className="input-light" maxLength={200}
|
||||
ref = {titleRef}
|
||||
value={qnaData?.title || '' }
|
||||
onChange={(e) => {setQnaData({ ...qnaData, title: e.target.value })}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="design-request-grid mt15">
|
||||
<div className="design-request-count">
|
||||
<div className="design-request-grid-tit">{getMessage("qna.reg.header.contents")} <span className="red">*</span></div>
|
||||
</div>
|
||||
<div>
|
||||
<textarea className="textarea-form" name="" id="" maxLength={4000}
|
||||
ref={contentsRef}
|
||||
value={qnaData?.contents || '' }
|
||||
onChange={(e) => {setQnaData({ ...qnaData, contents: e.target.value })}} ></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<QnaFileUploader {...fileUploadProps} qnaData={qnaData} setQnaData={setQnaData} />
|
||||
|
||||
</div>
|
||||
<div className="footer-btn-wrap">
|
||||
{isBtnDisable === false && <button className="btn-origin navy mr5" onClick={handleQnaSubmit}>{getMessage("qna.reg.header.save")}</button>}
|
||||
<button className="btn-origin grey" onClick={() => {
|
||||
setOpen(false)
|
||||
}}>{getMessage("board.sub.btn.close")}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -13,7 +13,7 @@ import dayjs from 'dayjs'
|
||||
import { useCommonCode } from '@/hooks/common/useCommonCode'
|
||||
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
|
||||
import { SessionContext } from '@/app/SessionProvider'
|
||||
import Select from 'react-select'
|
||||
import Select, { components } from 'react-select'
|
||||
import { convertNumberToPriceDecimal, convertNumberToPriceDecimalToFixed } from '@/util/common-utils'
|
||||
import ProductFeaturesPop from './popup/ProductFeaturesPop'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
@ -23,6 +23,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)
|
||||
@ -60,7 +61,7 @@ export default function Estimate({}) {
|
||||
|
||||
const [cableItemList, setCableItemList] = useState([]) //케이블 리스트
|
||||
const [cableItem, setCableItem] = useState('') //케이블 선택값
|
||||
|
||||
const [cableDbItem, setCableDbItem] = useState('') //케이블 선택값
|
||||
const [startDate, setStartDate] = useState(new Date())
|
||||
const singleDatePickerProps = {
|
||||
startDate,
|
||||
@ -98,7 +99,7 @@ export default function Estimate({}) {
|
||||
}
|
||||
|
||||
const initEstimate = (currPid = currentPid) => {
|
||||
console.log('🚀 ~ initEstimate ~ currPid:', currPid)
|
||||
// console.log('🚀 ~ initEstimate ~ currPid:', currPid)
|
||||
closeAll()
|
||||
setObjectNo(objectRecoil.floorPlanObjectNo)
|
||||
|
||||
@ -117,6 +118,7 @@ export default function Estimate({}) {
|
||||
item.value = item.clRefChr1
|
||||
item.label = item.clRefChr2
|
||||
})
|
||||
// console.log(code2)
|
||||
setCableItemList(code2)
|
||||
}
|
||||
|
||||
@ -136,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)
|
||||
}
|
||||
})
|
||||
@ -151,9 +173,22 @@ export default function Estimate({}) {
|
||||
})
|
||||
}
|
||||
|
||||
const groupStyles = {
|
||||
groupHeading: (provided) => ({
|
||||
...provided,
|
||||
fontSize: '14px',
|
||||
fontWeight: 'bold',
|
||||
color: '#333',
|
||||
backgroundColor: '#f5f5f5',
|
||||
padding: '8px 12px',
|
||||
marginBottom: '4px',
|
||||
borderBottom: '2px solid #ddd'
|
||||
})
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log('🚀 ~ Estimate ~ selectedPlan:', selectedPlan)
|
||||
if (selectedPlan) initEstimate(selectedPlan.planNo)
|
||||
// console.log('🚀 ~ Estimate ~ selectedPlan:', selectedPlan)
|
||||
if (selectedPlan) initEstimate(selectedPlan?.planNo?? currentPid)
|
||||
}, [selectedPlan])
|
||||
|
||||
useEffect(() => {
|
||||
@ -175,7 +210,10 @@ export default function Estimate({}) {
|
||||
row.check = false
|
||||
estimateOption.map((row2) => {
|
||||
if (row.pkgYn === '0') {
|
||||
if (row2 === row.code) {
|
||||
// if (row2 === row.code) {
|
||||
// row.check = true
|
||||
// }
|
||||
if (row.code.split('、').includes(row2)) {
|
||||
row.check = true
|
||||
}
|
||||
} else {
|
||||
@ -217,7 +255,10 @@ export default function Estimate({}) {
|
||||
row.check = false
|
||||
estimateOption.map((row2) => {
|
||||
if (row.pkgYn === '0') {
|
||||
if (row2 === row.code) {
|
||||
// if (row2 === row.code) {
|
||||
// row.check = true
|
||||
// }
|
||||
if (row.code.split('、').includes(row2)) {
|
||||
row.check = true
|
||||
}
|
||||
} else {
|
||||
@ -240,7 +281,6 @@ export default function Estimate({}) {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
setSpecialNoteList(res)
|
||||
|
||||
setSpecialNoteFirstFlg(true)
|
||||
@ -377,8 +417,8 @@ export default function Estimate({}) {
|
||||
useEffect(() => {
|
||||
if (estimateContextState.estimateType !== '') {
|
||||
const param = {
|
||||
saleStoreId: session.storeId,
|
||||
sapSalesStoreCd: session.custCd,
|
||||
saleStoreId: estimateContextState.sapSaleStoreId,
|
||||
sapSalesStoreCd: estimateContextState.sapSalesStoreCd,
|
||||
docTpCd: estimateContextState?.estimateType,
|
||||
}
|
||||
|
||||
@ -387,6 +427,8 @@ export default function Estimate({}) {
|
||||
if (isNotEmptyArray(res?.data)) {
|
||||
setStorePriceList(res.data)
|
||||
}
|
||||
|
||||
setItemChangeYn(true)
|
||||
})
|
||||
|
||||
if (estimateContextState.estimateType === 'YJSS') {
|
||||
@ -416,8 +458,6 @@ export default function Estimate({}) {
|
||||
handlePricing('UNIT_PRICE')
|
||||
}
|
||||
}
|
||||
|
||||
setItemChangeYn(true)
|
||||
}
|
||||
}, [estimateContextState?.estimateType])
|
||||
|
||||
@ -469,6 +509,21 @@ export default function Estimate({}) {
|
||||
} else {
|
||||
item.check = false
|
||||
}
|
||||
} else {
|
||||
let codes = item.code.split('、')
|
||||
let flg = '0'
|
||||
if (codes.length > 1) {
|
||||
for (let i = 0; i < pushData.length; i++) {
|
||||
if (codes.indexOf(pushData[i]) > -1) {
|
||||
flg = '1'
|
||||
}
|
||||
}
|
||||
if (flg === '1') {
|
||||
item.check = true
|
||||
} else {
|
||||
item.check = false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -478,12 +533,27 @@ export default function Estimate({}) {
|
||||
})
|
||||
}
|
||||
|
||||
//Pricing 버튼클릭시 confirm 노출
|
||||
const handlePricingBtn = (showPriceCd) => {
|
||||
swalFire({
|
||||
text: getMessage('estimate.detail.showPrice.pricingBtn.confirm'),
|
||||
type: 'confirm',
|
||||
icon: 'warning',
|
||||
confirmFn: () => {
|
||||
handlePricing(showPriceCd)
|
||||
setEstimateContextState({ pricingFlag:true })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
//Pricing 버튼
|
||||
const handlePricing = async (showPriceCd) => {
|
||||
const param = {
|
||||
saleStoreId: session.storeId,
|
||||
sapSalesStoreCd: session.custCd,
|
||||
saleStoreId: estimateContextState.sapSaleStoreId,
|
||||
sapSalesStoreCd: estimateContextState.sapSalesStoreCd,
|
||||
docTpCd: estimateContextState.estimateType,
|
||||
secSapSalesStoreCd:
|
||||
estimateContextState.secSapSalesStoreCd?.length > 0 && showPriceCd === 'QSP_PRICE' ? estimateContextState.secSapSalesStoreCd : '',
|
||||
priceCd: showPriceCd,
|
||||
itemIdList: estimateContextState.itemList.filter((item) => item.delFlg === '0' && item.paDispOrder === null),
|
||||
}
|
||||
@ -506,7 +576,6 @@ export default function Estimate({}) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
setIsGlobalLoading(true)
|
||||
await promisePost({ url: '/api/estimate/price/item-price-list', data: param }).then((res) => {
|
||||
let updateList = []
|
||||
@ -531,6 +600,7 @@ export default function Estimate({}) {
|
||||
updateList.push({
|
||||
...item,
|
||||
openFlg: data.data2[i].unitPrice === '0.0' ? '1' : '0',
|
||||
unitOpenFlg: (showPriceCd === 'QSP_PRICE' && item.openFlg === '1') ? '1' : '0',
|
||||
salePrice: data.data2[i].unitPrice === null ? '0' : data.data2[i].unitPrice,
|
||||
saleTotPrice: (item.amount * data.data2[i].unitPrice).toString(),
|
||||
})
|
||||
@ -609,11 +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,
|
||||
@ -627,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'
|
||||
@ -665,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 {
|
||||
@ -696,7 +770,7 @@ export default function Estimate({}) {
|
||||
/* 케이블 select 변경시 */
|
||||
const onChangeDisplayCableItem = (value, itemList) => {
|
||||
itemList.map((item, index) => {
|
||||
if (item.dispCableFlg === '1') {
|
||||
if (item.dispCableFlg === '1' && item.itemTpCd !== 'M12' && item.itemTpCd !== 'S13') {
|
||||
if (value !== '') {
|
||||
onChangeDisplayItem(value, item.dispOrder, index, true)
|
||||
}
|
||||
@ -705,6 +779,18 @@ export default function Estimate({}) {
|
||||
setCableItem(value)
|
||||
}
|
||||
|
||||
/* 케이블 select 변경시 */
|
||||
const onChangeDisplayDoubleCableItem = (value, itemList) => {
|
||||
itemList.map((item, index) => {
|
||||
if (item.dispCableFlg === '1' && (item.itemTpCd === 'M12' || item.itemTpCd === 'S13')) {
|
||||
if (value !== '') {
|
||||
onChangeDisplayItem(value, item.dispOrder, index, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
setCableDbItem(value)
|
||||
}
|
||||
|
||||
// 아이템 자동완성 검색시 아이템 추가/변경시
|
||||
const onChangeDisplayItem = (itemId, dispOrder, index, flag) => {
|
||||
const param = {
|
||||
@ -892,7 +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
|
||||
@ -927,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
|
||||
@ -961,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
|
||||
@ -1021,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
|
||||
@ -1056,18 +1142,24 @@ export default function Estimate({}) {
|
||||
|
||||
if (item.dispCableFlg === '1') {
|
||||
dispCableFlgCnt++
|
||||
setCableItem(item.itemId)
|
||||
if(item.itemTpCd === 'M12' || item.itemTpCd === 'S13') {
|
||||
setCableDbItem(item.itemId)
|
||||
}else{
|
||||
setCableItem(item.itemId)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (dispCableFlgCnt === 0) {
|
||||
setCableItem('100038')
|
||||
setCableDbItem('100037')
|
||||
}
|
||||
|
||||
let pkgAsp = estimateContextState.pkgAsp ? Number(estimateContextState.pkgAsp.replaceAll(',', '')) : 0
|
||||
|
||||
// let pkgAsp = estimateContextState.pkgAsp ? Number(estimateContextState.pkgAsp.replaceAll(',', '')) : 0
|
||||
const pkgAsp = Number(normalizeDecimal(estimateContextState.pkgAsp))
|
||||
totals.pkgTotPrice = pkgAsp * totals.totVolKw * 1000
|
||||
|
||||
totals.supplyPrice = totals.addSupplyPrice + totals.pkgTotPrice
|
||||
totals.vatPrice = totals.supplyPrice * 0.1
|
||||
totals.totPrice = totals.supplyPrice + totals.vatPrice
|
||||
@ -1097,7 +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
|
||||
@ -1123,16 +1215,18 @@ export default function Estimate({}) {
|
||||
|
||||
if (item.dispCableFlg === '1') {
|
||||
dispCableFlgCnt++
|
||||
}
|
||||
|
||||
if (item.dispCableFlg === '1') {
|
||||
setCableItem(item.itemId)
|
||||
if(item.itemTpCd === 'M12' || item.itemTpCd === 'S13') {
|
||||
setCableDbItem(item.itemId)
|
||||
}else{
|
||||
setCableItem(item.itemId)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (dispCableFlgCnt === 0) {
|
||||
setCableItem('100038')
|
||||
setCableDbItem('100037')
|
||||
}
|
||||
|
||||
totals.vatPrice = totals.supplyPrice * 0.1
|
||||
@ -1193,10 +1287,26 @@ export default function Estimate({}) {
|
||||
|
||||
if (dispCableFlgCnt === 0) {
|
||||
setCableItem('100038')
|
||||
setCableDbItem('100037')
|
||||
}
|
||||
}
|
||||
}, [estimateContextState?.itemList, cableItemList])
|
||||
|
||||
const [agencyCustList, setAgencyCustList] = useState([])
|
||||
useEffect(() => {
|
||||
// 952 - 2차점 특가 sapSalesStoreCd
|
||||
if (estimateContextState?.sapSalesStoreCd && session?.storeLvl === '1') {
|
||||
const param = {
|
||||
sapSalesStoreCd: estimateContextState.sapSalesStoreCd,
|
||||
}
|
||||
const apiUrl = `api/estimate/agency-cust-list?${queryStringFormatter(param)}`
|
||||
get({ url: apiUrl }).then((res) => {
|
||||
if (isNotEmptyArray(res?.data)) {
|
||||
setAgencyCustList(res?.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [estimateContextState?.sapSalesStoreCd])
|
||||
return (
|
||||
<div className="sub-content estimate">
|
||||
<div className="sub-content-inner">
|
||||
@ -1207,7 +1317,7 @@ export default function Estimate({}) {
|
||||
<div className="estimate-box">
|
||||
<div className="estimate-tit">{getMessage('estimate.detail.objectNo')}</div>
|
||||
<div className="estimate-name">
|
||||
{currentObjectNo} (Plan No: {planNo})
|
||||
{currentObjectNo} (Plan No: {currentPid})
|
||||
</div>
|
||||
</div>
|
||||
<div className="estimate-box">
|
||||
@ -1341,36 +1451,79 @@ export default function Estimate({}) {
|
||||
{getMessage('estimate.detail.estimateType')} <span className="important">*</span>
|
||||
</th>
|
||||
<td colSpan={3}>
|
||||
<div className="radio-wrap">
|
||||
<div className="d-check-radio light mr10">
|
||||
<input
|
||||
type="radio"
|
||||
name="estimateType"
|
||||
id="YJSS"
|
||||
value={'YJSS'}
|
||||
checked={estimateContextState?.estimateType === 'YJSS' ? true : false}
|
||||
onChange={(e) => {
|
||||
//주문분류
|
||||
setHandlePricingFlag(true)
|
||||
setEstimateContextState({ estimateType: e.target.value })
|
||||
<div className="form-flex-wrap">
|
||||
<div className="radio-wrap">
|
||||
{/*pkgRank is null, empty 인 경우 : 사용불가, 이전에 등록된 경우 사용가능, style로 제어*/}
|
||||
<div
|
||||
className="d-check-radio light mr10"
|
||||
style={{
|
||||
display:
|
||||
(isNotEmptyArray(storePriceList) > 0 && storePriceList[0].pkgRank !== null && storePriceList[0].pkgRank !== '') ||
|
||||
estimateContextState?.estimateType === 'YJSS'
|
||||
? ''
|
||||
: 'none',
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="YJSS">{getMessage('estimate.detail.estimateType.yjss')}</label>
|
||||
</div>
|
||||
<div className="d-check-radio light">
|
||||
<input
|
||||
type="radio"
|
||||
name="estimateType"
|
||||
id="YJOD"
|
||||
value={'YJOD'}
|
||||
checked={estimateContextState?.estimateType === 'YJOD' ? true : false}
|
||||
onChange={(e) => {
|
||||
setHandlePricingFlag(true)
|
||||
setEstimateContextState({ estimateType: e.target.value })
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="YJOD">{getMessage('estimate.detail.estimateType.yjod')}</label>
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="estimateType"
|
||||
id="YJSS"
|
||||
value={'YJSS'}
|
||||
checked={estimateContextState?.estimateType === 'YJSS' ? true : false}
|
||||
onChange={(e) => {
|
||||
//주문분류
|
||||
setHandlePricingFlag(true)
|
||||
setEstimateContextState({ estimateType: e.target.value, setEstimateContextState })
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="YJSS">{getMessage('estimate.detail.estimateType.yjss')}</label>
|
||||
</div>
|
||||
<div className="d-check-radio light">
|
||||
<input
|
||||
type="radio"
|
||||
name="estimateType"
|
||||
id="YJOD"
|
||||
value={'YJOD'}
|
||||
checked={estimateContextState?.estimateType === 'YJOD' ? true : false}
|
||||
onChange={(e) => {
|
||||
setHandlePricingFlag(true)
|
||||
setEstimateContextState({ estimateType: e.target.value })
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="YJOD">{getMessage('estimate.detail.estimateType.yjod')}</label>
|
||||
</div>
|
||||
</div>
|
||||
{session?.storeLvl === '100000' && agencyCustList.length > 0 ? ( //일시적으로 1 => 100000로 정리
|
||||
<div className="form-flex-select ml10">
|
||||
<label htmlFor="">{getMessage('estimate.detail.agency')}</label>
|
||||
<div className="select-wrap" style={{ width: '400px' }}>
|
||||
<Select
|
||||
id="agencyName"
|
||||
instanceId="agencyName"
|
||||
className="react-select-custom"
|
||||
classNamePrefix="custom"
|
||||
placeholder="Select"
|
||||
options={agencyCustList}
|
||||
onChange={(e) => {
|
||||
if (isObjectNotEmpty(e)) {
|
||||
setEstimateContextState({ secSapSalesStoreCd: e.sapSalesStoreCd })
|
||||
} else {
|
||||
setEstimateContextState({ secSapSalesStoreCd: '' })
|
||||
}
|
||||
}}
|
||||
getOptionLabel={(x) => x.sapSalesStoreNm}
|
||||
getOptionValue={(x) => x.sapSalesStoreCd}
|
||||
isClearable={true}
|
||||
isSearchable={true}
|
||||
value={agencyCustList.filter(function (option) {
|
||||
return option.sapSalesStoreCd === estimateContextState.secSapSalesStoreCd
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@ -1729,15 +1882,17 @@ export default function Estimate({}) {
|
||||
<button
|
||||
type="button"
|
||||
className="btn-origin grey ml5"
|
||||
onClick={() => {
|
||||
onClick={(event) => {
|
||||
|
||||
setHandlePricingFlag(true)
|
||||
handlePricing(showPriceCd)
|
||||
handlePricingBtn(showPriceCd)
|
||||
}}
|
||||
>
|
||||
{getMessage('estimate.detail.showPrice.pricingBtn')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="product-price-wrap ml10">
|
||||
<div className="product-price-tit">{getMessage('estimate.detail.header.singleCable')}</div>
|
||||
<div className="select-wrap">
|
||||
<select
|
||||
className="select-light"
|
||||
@ -1747,11 +1902,34 @@ export default function Estimate({}) {
|
||||
value={cableItem}
|
||||
>
|
||||
{cableItemList.length > 0 &&
|
||||
cableItemList.map((row) => (
|
||||
<option key={row.clRefChr1} value={row.clRefChr1}>
|
||||
{row.clRefChr2}
|
||||
</option>
|
||||
))}
|
||||
cableItemList.map((row) => {
|
||||
if(!row.clRefChr2.includes('S')){
|
||||
return <option key={row.clRefChr1} value={row.clRefChr1}>
|
||||
{row.clRefChr2}
|
||||
</option>
|
||||
}
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="product-price-wrap ml10">
|
||||
<div className="product-price-tit">{getMessage('estimate.detail.header.doubleCable')}</div>
|
||||
<div className="select-wrap">
|
||||
<select
|
||||
className="select-light"
|
||||
onChange={(e) => {
|
||||
onChangeDisplayDoubleCableItem(e.target.value, estimateContextState.itemList)
|
||||
}}
|
||||
value={cableDbItem}
|
||||
>
|
||||
{cableItemList.length > 0 &&
|
||||
cableItemList.map((row) => {
|
||||
if(row.clRefChr2.includes('S')){
|
||||
return <option key={row.clRefChr1} value={row.clRefChr1}>
|
||||
{row.clRefChr2.replace('S','')}
|
||||
</option>
|
||||
}
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -1834,7 +2012,7 @@ export default function Estimate({}) {
|
||||
<input
|
||||
type="checkbox"
|
||||
id={item?.dispOrder}
|
||||
disabled={!!item?.paDispOrder || item.dispCableFlg === '1'}
|
||||
disabled={!!item?.paDispOrder || item.dispCableFlg === '1X'}
|
||||
onChange={() => onChangeSelect(item.dispOrder)}
|
||||
checked={!!selection.has(item.dispOrder)}
|
||||
/>
|
||||
@ -1853,14 +2031,20 @@ export default function Estimate({}) {
|
||||
classNamePrefix="custom"
|
||||
placeholder="Select"
|
||||
options={originDisplayItemList}
|
||||
styles={groupStyles}
|
||||
onChange={(e) => {
|
||||
if (isObjectNotEmpty(e)) {
|
||||
onChangeDisplayItem(e.itemId, item.dispOrder, index, false)
|
||||
}
|
||||
}}
|
||||
menuPlacement={'auto'}
|
||||
getOptionLabel={(x) => x.itemName}
|
||||
getOptionLabel={(x) => x.itemName + ' (' + x.itemNo + ')'}
|
||||
getOptionValue={(x) => x.itemNo}
|
||||
components={{
|
||||
SingleValue: ({ children, ...props }) => {
|
||||
return <components.SingleValue {...props}>{props.data.itemName}</components.SingleValue>
|
||||
},
|
||||
}}
|
||||
isClearable={false}
|
||||
isDisabled={!!item?.paDispOrder}
|
||||
value={displayItemList.filter(function (option) {
|
||||
@ -1880,12 +2064,17 @@ export default function Estimate({}) {
|
||||
placeholder="Select"
|
||||
options={cableItemList}
|
||||
menuPlacement={'auto'}
|
||||
getOptionLabel={(x) => x.clRefChr3}
|
||||
getOptionLabel={(x) => x.clRefChr3 + ' (' + x.clRefChr1 + ')'}
|
||||
getOptionValue={(x) => x.clRefChr1}
|
||||
components={{
|
||||
SingleValue: ({ children, ...props }) => {
|
||||
return <components.SingleValue {...props}>{(item.itemTpCd === 'M12' || item.itemTpCd === 'S13')? item.itemName : props.data.clRefChr3}</components.SingleValue>
|
||||
},
|
||||
}}
|
||||
isClearable={false}
|
||||
isDisabled={true}
|
||||
value={cableItemList.filter(function (option) {
|
||||
return option.clRefChr1 === item.itemId
|
||||
return (item.itemTpCd === 'M12' || item.itemTpCd === 'S13' )? item.itemId : option.clRefChr1 === item.itemId
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
@ -1936,7 +2125,11 @@ export default function Estimate({}) {
|
||||
<input
|
||||
type="text"
|
||||
className="input-light al-r"
|
||||
value={convertNumberToPriceDecimal(item?.showSalePrice === '0' ? null : item?.salePrice?.replaceAll(',', ''))}
|
||||
value={
|
||||
item.openFlg === '1'
|
||||
? 'OPEN'
|
||||
: convertNumberToPriceDecimal(item?.showSalePrice === '0' ? null : item?.salePrice?.replaceAll(',', ''))
|
||||
}
|
||||
disabled={
|
||||
item.openFlg === '1'
|
||||
? true
|
||||
@ -1964,15 +2157,17 @@ export default function Estimate({}) {
|
||||
</div>
|
||||
</td>
|
||||
<td className="al-r">
|
||||
{convertNumberToPriceDecimal(
|
||||
item?.showSaleTotPrice === '0'
|
||||
? null
|
||||
: item?.amount === ''
|
||||
? null
|
||||
: item?.saleTotPrice === '0'
|
||||
{item?.openFlg === '1'
|
||||
? 'OPEN'
|
||||
: convertNumberToPriceDecimal(
|
||||
item?.showSaleTotPrice === '0'
|
||||
? null
|
||||
: item?.saleTotPrice?.replaceAll(',', ''),
|
||||
)}
|
||||
: item?.amount === ''
|
||||
? null
|
||||
: item?.saleTotPrice === '0'
|
||||
? null
|
||||
: item?.saleTotPrice?.replaceAll(',', ''),
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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',
|
||||
@ -17,7 +19,7 @@ export const QLine = fabric.util.createClass(fabric.Line, {
|
||||
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 +33,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 +70,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 +177,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
|
||||
},
|
||||
})
|
||||
|
||||
@ -2,10 +2,11 @@ import { fabric } from 'fabric'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { QLine } from '@/components/fabric/QLine'
|
||||
import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util'
|
||||
import { calculateAngle, drawGabledRoof, drawRidgeRoof, drawShedRoof, toGeoJSON } from '@/util/qpolygon-utils'
|
||||
import { calculateAngle, drawGableRoof, 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',
|
||||
@ -29,12 +30,13 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
this.texts = []
|
||||
this.hips = []
|
||||
this.ridges = []
|
||||
this.connectRidges = []
|
||||
this.cells = []
|
||||
this.innerLines = []
|
||||
this.children = []
|
||||
this.separatePolygon = []
|
||||
this.toFixed = options.toFixed ?? 1
|
||||
this.baseLines = []
|
||||
// this.colorLines = []
|
||||
|
||||
// 소수점 전부 제거
|
||||
points.forEach((point) => {
|
||||
@ -45,8 +47,11 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
options.sort = options.sort ?? true
|
||||
options.parentId = options.parentId ?? null
|
||||
|
||||
this.isSortedPoints = false
|
||||
|
||||
if (!options.sort && points.length <= 8) {
|
||||
points = sortedPointLessEightPoint(points)
|
||||
this.isSortedPoints = true
|
||||
} else {
|
||||
let isDiagonal = false
|
||||
points.forEach((point, i) => {
|
||||
@ -62,6 +67,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
|
||||
if (!isDiagonal) {
|
||||
points = sortedPoints(points)
|
||||
this.isSortedPoints = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,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() {
|
||||
@ -119,11 +129,15 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
this.addLengthText()
|
||||
|
||||
this.on('moving', () => {
|
||||
this.initLines()
|
||||
this.addLengthText()
|
||||
this.setCoords()
|
||||
})
|
||||
|
||||
this.on('modified', (e) => {
|
||||
this.initLines()
|
||||
this.addLengthText()
|
||||
this.setCoords()
|
||||
})
|
||||
|
||||
this.on('selected', () => {
|
||||
@ -183,8 +197,8 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
|
||||
this.lines = []
|
||||
|
||||
this.points.forEach((point, i) => {
|
||||
const nextPoint = this.points[(i + 1) % this.points.length]
|
||||
this.getCurrentPoints().forEach((point, i) => {
|
||||
const nextPoint = this.getCurrentPoints()[(i + 1) % this.points.length]
|
||||
const line = new QLine([point.x, point.y, nextPoint.x, nextPoint.y], {
|
||||
stroke: this.stroke,
|
||||
strokeWidth: this.strokeWidth,
|
||||
@ -203,14 +217,49 @@ 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))
|
||||
},
|
||||
|
||||
/**
|
||||
* 보조선 그리기
|
||||
* @param settingModalFirstOptions
|
||||
*/
|
||||
drawHelpLine(settingModalFirstOptions) {
|
||||
/* innerLines 초기화 */
|
||||
this.canvas
|
||||
.getObjects()
|
||||
.filter(
|
||||
(obj) =>
|
||||
obj.parentId === this.id &&
|
||||
obj.name !== POLYGON_TYPE.WALL &&
|
||||
obj.name !== POLYGON_TYPE.ROOF &&
|
||||
obj.name !== 'lengthText' &&
|
||||
obj.name !== 'outerLine' &&
|
||||
obj.name !== 'baseLine',
|
||||
// && obj.name !== 'outerLinePoint',
|
||||
)
|
||||
.forEach((obj) => this.canvas.remove(obj))
|
||||
this.innerLines = []
|
||||
this.canvas.renderAll()
|
||||
|
||||
let textMode = 'plane'
|
||||
|
||||
const dimensionDisplay = settingModalFirstOptions?.dimensionDisplay.find((opt) => opt.selected).id
|
||||
@ -228,60 +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 eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE]
|
||||
const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD]
|
||||
const isGableRoof = function (types) {
|
||||
if (!types.includes(LINE_TYPE.WALLLINE.GABLE)) {
|
||||
return false
|
||||
}
|
||||
const gableTypes = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD]
|
||||
const oddTypes = types.filter((type, i) => i % 2 === 0)
|
||||
const evenTypes = types.filter((type, i) => i % 2 === 1)
|
||||
|
||||
// const isEaves = types.every((type) => eavesType.includes(type))
|
||||
const gableOdd = types.filter((type, i) => i % 2 === 0)
|
||||
const gableEven = types.filter((type, i) => i % 2 === 1)
|
||||
const hasShed = types.includes(LINE_TYPE.WALLLINE.SHED)
|
||||
const oddAllEaves = oddTypes.every((type) => type === LINE_TYPE.WALLLINE.EAVES)
|
||||
const evenAllGable = evenTypes.every((type) => gableTypes.includes(type))
|
||||
const evenAllEaves = evenTypes.every((type) => type === LINE_TYPE.WALLLINE.EAVES)
|
||||
const oddAllGable = oddTypes.every((type) => gableTypes.includes(type))
|
||||
|
||||
// A형, B형 박공 지붕
|
||||
if (
|
||||
(gableOdd.every((type) => type === LINE_TYPE.WALLLINE.EAVES) && gableEven.every((type) => gableType.includes(type))) ||
|
||||
(gableEven.every((type) => type === LINE_TYPE.WALLLINE.EAVES) && gableOdd.every((type) => gableType.includes(type)))
|
||||
) {
|
||||
drawGabledRoof(this.id, this.canvas, textMode)
|
||||
} else if (hasShed) {
|
||||
const sheds = this.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.SHED)
|
||||
const areLinesParallel = function (line1, line2) {
|
||||
const angle1 = calculateAngle(line1.startPoint, line1.endPoint)
|
||||
const angle2 = calculateAngle(line2.startPoint, line2.endPoint)
|
||||
return 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)
|
||||
}
|
||||
},
|
||||
@ -366,7 +431,15 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
this.canvas = canvas
|
||||
},
|
||||
fillCellABType(
|
||||
cell = { width: 50, height: 100, padding: 5, wallDirection: 'left', referenceDirection: 'none', startIndex: -1, isCellCenter: false },
|
||||
cell = {
|
||||
width: 50,
|
||||
height: 100,
|
||||
padding: 5,
|
||||
wallDirection: 'left',
|
||||
referenceDirection: 'none',
|
||||
startIndex: -1,
|
||||
isCellCenter: false,
|
||||
},
|
||||
) {
|
||||
const points = this.points
|
||||
|
||||
@ -668,13 +741,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)
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -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'
|
||||
@ -13,7 +13,7 @@ import { useContextMenu } from '@/hooks/useContextMenu'
|
||||
import { useCanvasConfigInitialize } from '@/hooks/common/useCanvasConfigInitialize'
|
||||
import { currentMenuState } from '@/store/canvasAtom'
|
||||
import { totalDisplaySelector } from '@/store/settingAtom'
|
||||
import { MENU, POLYGON_TYPE } from '@/common/common'
|
||||
import { POLYGON_TYPE } from '@/common/common'
|
||||
import { FloorPlanContext } from '@/app/floor-plan/FloorPlanProvider'
|
||||
import { QcastContext } from '@/app/QcastProvider'
|
||||
import {
|
||||
@ -30,11 +30,14 @@ import { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
|
||||
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
|
||||
import { useEvent } from '@/hooks/useEvent'
|
||||
import { compasDegAtom } from '@/store/orientationAtom'
|
||||
import { hotkeyStore } from '@/store/hotkeyAtom'
|
||||
import { usePopup } from '@/hooks/usePopup'
|
||||
|
||||
export default function CanvasFrame() {
|
||||
const canvasRef = useRef(null)
|
||||
const { canvas } = useCanvas('canvas')
|
||||
const { canvasLoadInit, gridInit } = useCanvasConfigInitialize()
|
||||
const { closeAll } = usePopup()
|
||||
const currentMenu = useRecoilValue(currentMenuState)
|
||||
const { floorPlanState } = useContext(FloorPlanContext)
|
||||
const { contextMenu, handleClick } = useContextMenu()
|
||||
@ -65,14 +68,37 @@ 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 {
|
||||
setSelectedMenu(null)
|
||||
}
|
||||
Object.keys(currentCanvasPlan).length > 0 && canvas && handleModuleSelectionTotal()
|
||||
} else {
|
||||
setSelectedMenu(null)
|
||||
}
|
||||
gridInit()
|
||||
}
|
||||
@ -92,6 +118,8 @@ export default function CanvasFrame() {
|
||||
|
||||
useEffect(() => {
|
||||
setIsGlobalLoading(false)
|
||||
// 혹시 모를 팝업이 떠있는 경우 닫고 시작한다.
|
||||
closeAll()
|
||||
|
||||
return () => {
|
||||
canvas?.clear()
|
||||
@ -110,6 +138,38 @@ export default function CanvasFrame() {
|
||||
resetPcsCheckState()
|
||||
}
|
||||
|
||||
/**
|
||||
* 캔버스가 있을 경우 핫키 이벤트 처리
|
||||
* hotkeyStore에 핫키 이벤트 리스너 추가
|
||||
*
|
||||
* const hotkeys = [
|
||||
{ key: 'c', fn: () => asdf() },
|
||||
{ key: 'v', fn: () => qwer() },
|
||||
]
|
||||
setHotkeyStore(hotkeys)
|
||||
*/
|
||||
const hotkeyState = useRecoilValue(hotkeyStore)
|
||||
const hotkeyHandlerRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
hotkeyHandlerRef.current = (e) => {
|
||||
hotkeyState.forEach((hotkey) => {
|
||||
if (e.key === hotkey.key) {
|
||||
hotkey.fn()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
document.addEventListener('keyup', hotkeyHandlerRef.current)
|
||||
|
||||
return () => {
|
||||
if (hotkeyHandlerRef.current) {
|
||||
document.removeEventListener('keyup', hotkeyHandlerRef.current)
|
||||
}
|
||||
}
|
||||
}, [hotkeyState])
|
||||
/** 핫키 이벤트 처리 끝 */
|
||||
|
||||
return (
|
||||
<div className="canvas-frame">
|
||||
<canvas ref={canvasRef} id="canvas" style={{ position: 'relative' }}></canvas>
|
||||
|
||||
@ -31,12 +31,13 @@ export default function CanvasLayout({ children }) {
|
||||
return (
|
||||
<div className="canvas-layout">
|
||||
<div className={`canvas-page-list ${['outline', 'surface', 'module'].includes(selectedMenu) ? 'active' : ''}`}>
|
||||
<div className="canvas-id">{objectNo}</div>
|
||||
<div className="canvas-plane-wrap">
|
||||
{plans.map((plan, index) => (
|
||||
<button
|
||||
key={`plan-${plan.id}`}
|
||||
className={`canvas-page-box ${plan.isCurrent === true ? 'on' : ''}`}
|
||||
onClick={() => handleCurrentPlan(plan.id)}
|
||||
onClick={() => (plan.isCurrent ? '' : handleCurrentPlan(plan.id))}
|
||||
>
|
||||
<span>{`Plan ${plan.planNo}`}</span>
|
||||
{plans.length > 1 && !pathname.includes('/estimate') && !pathname.includes('/simulator') && (
|
||||
|
||||
@ -4,7 +4,7 @@ import { useContext, useEffect, useState } from 'react'
|
||||
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
|
||||
import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
@ -25,7 +25,7 @@ import { useCommonUtils } from '@/hooks/common/useCommonUtils'
|
||||
import useMenu from '@/hooks/common/useMenu'
|
||||
import { useEstimateController } from '@/hooks/floorPlan/estimate/useEstimateController'
|
||||
import { useAxios } from '@/hooks/useAxios'
|
||||
import { canvasSettingState, canvasState, canvasZoomState, currentMenuState, verticalHorizontalModeState } from '@/store/canvasAtom'
|
||||
import { canvasSettingState, canvasState, canvasZoomState, currentMenuState, verticalHorizontalModeState, currentCanvasPlanState } from '@/store/canvasAtom'
|
||||
import { sessionStore } from '@/store/commonAtom'
|
||||
import { outerLinePointsState } from '@/store/outerLineAtom'
|
||||
import { appMessageStore, globalLocaleStore } from '@/store/localeAtom'
|
||||
@ -50,7 +50,9 @@ import JA from '@/locales/ja.json'
|
||||
import { QcastContext } from '@/app/QcastProvider'
|
||||
import { useRoofFn } from '@/hooks/common/useRoofFn'
|
||||
import { usePolygon } from '@/hooks/usePolygon'
|
||||
import { useTrestle } from '@/hooks/module/useTrestle'
|
||||
export default function CanvasMenu(props) {
|
||||
const [currentCanvasPlan, setCurrentCanvasPlan] = useRecoilState(currentCanvasPlanState)
|
||||
const { selectedMenu, setSelectedMenu } = props
|
||||
const pathname = usePathname()
|
||||
const router = useRouter()
|
||||
@ -67,6 +69,7 @@ export default function CanvasMenu(props) {
|
||||
const globalLocale = useRecoilValue(globalLocaleStore)
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
const { handleZoomClear, handleZoom } = useCanvasEvent()
|
||||
const { setAllModuleSurfaceIsComplete, isAllComplete } = useTrestle()
|
||||
const { handleMenu } = useMenu()
|
||||
// const urlParams = useSearchParams()
|
||||
const { handleEstimateSubmit, fetchSetting, estimateContextState, setEstimateContextState } = useEstimateController()
|
||||
@ -96,7 +99,7 @@ export default function CanvasMenu(props) {
|
||||
const [lockButtonStyle, setLockButtonStyle] = useState('') //잠금 버튼
|
||||
|
||||
const setFloorPlanObjectNo = useSetRecoilState(floorPlanObjectState) //견적서 화면용 물건번호리코일
|
||||
|
||||
const resetCommonUtils = useResetRecoilState(commonUtilsState)
|
||||
// 발전시뮬레이션 메뉴 이동
|
||||
const { objectNo, pid } = floorPlanState
|
||||
|
||||
@ -115,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,
|
||||
@ -164,6 +167,7 @@ export default function CanvasMenu(props) {
|
||||
}
|
||||
|
||||
const onClickNav = async (menu) => {
|
||||
resetCommonUtils()
|
||||
switch (menu.type) {
|
||||
case 'drawing':
|
||||
swalFire({
|
||||
@ -192,8 +196,17 @@ export default function CanvasMenu(props) {
|
||||
text: getMessage('module.delete.confirm'),
|
||||
type: 'confirm',
|
||||
confirmFn: () => {
|
||||
const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
|
||||
roofs.forEach((roof) => {
|
||||
roof.set({
|
||||
stroke: 'black',
|
||||
strokeWidth: 3,
|
||||
})
|
||||
})
|
||||
|
||||
//해당 메뉴 이동시 배치면 삭제
|
||||
|
||||
setAllModuleSurfaceIsComplete(false)
|
||||
const moduleSurfacesArray = canvas
|
||||
.getObjects()
|
||||
.filter((obj) => [POLYGON_TYPE.MODULE_SETUP_SURFACE, POLYGON_TYPE.MODULE, POLYGON_TYPE.OBJECT_SURFACE].includes(obj.name))
|
||||
@ -230,11 +243,18 @@ export default function CanvasMenu(props) {
|
||||
router.push(`/floor-plan?pid=${pid}&objectNo=${objectNo}`)
|
||||
setSelectedMenu('module')
|
||||
}
|
||||
await reloadCanvasStatus(objectNo, pid)
|
||||
await reloadCanvasStatus(objectNo, currentCanvasPlan?.planNo ?? pid)
|
||||
break
|
||||
case 'estimate':
|
||||
if (selectedMenu !== 'simulation') {
|
||||
if (!isAllComplete()) {
|
||||
swalFire({ text: getMessage('estimate.menu.move.valid1') })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
setIsGlobalLoading(true)
|
||||
promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan.planNo}/detail` }).then((res) => {
|
||||
promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan?.planNo ?? pid}/detail` }).then((res) => {
|
||||
if (res.status === 200) {
|
||||
const estimateDetail = res.data
|
||||
if (estimateDetail.estimateDate !== null) {
|
||||
@ -242,7 +262,7 @@ export default function CanvasMenu(props) {
|
||||
setCurrentMenu(menu.title)
|
||||
setFloorPlanObjectNo({ floorPlanObjectNo: objectNo })
|
||||
setIsGlobalLoading(false)
|
||||
router.push(`/floor-plan/estimate/5?pid=${selectedPlan.planNo}&objectNo=${objectNo}`)
|
||||
router.push(`/floor-plan/estimate/5?pid=${selectedPlan?.planNo ?? pid}&objectNo=${objectNo}`)
|
||||
if (pathname === '/floor-plan/estimate/5') {
|
||||
setIsGlobalLoading(false)
|
||||
}
|
||||
@ -255,13 +275,13 @@ export default function CanvasMenu(props) {
|
||||
break
|
||||
case 'simulation':
|
||||
setIsGlobalLoading(true)
|
||||
promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan.planNo}/detail` }).then((res) => {
|
||||
promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan?.planNo ?? pid}/detail` }).then((res) => {
|
||||
if (res.status === 200) {
|
||||
const estimateDetail = res.data
|
||||
if (estimateDetail.estimateDate !== null && estimateDetail.docNo) {
|
||||
setSelectedMenu(menu.type)
|
||||
setCurrentMenu(menu.title)
|
||||
router.push(`/floor-plan/simulator/6?pid=${selectedPlan.planNo}&objectNo=${objectNo}`)
|
||||
router.push(`/floor-plan/simulator/6?pid=${selectedPlan?.planNo ?? pid}&objectNo=${objectNo}`)
|
||||
if (pathname === '/floor-plan/simulator/6') {
|
||||
setIsGlobalLoading(false)
|
||||
}
|
||||
@ -303,7 +323,6 @@ export default function CanvasMenu(props) {
|
||||
const settingsModalOptions = useRecoilState(settingModalFirstOptionsState)
|
||||
|
||||
useEffect(() => {
|
||||
console.log(selectedMenu)
|
||||
if (selectedMenu === 'placement') {
|
||||
onClickPlacementInitialMenu()
|
||||
}
|
||||
@ -548,13 +567,26 @@ export default function CanvasMenu(props) {
|
||||
{
|
||||
<div className={`vertical-horizontal ${verticalHorizontalMode ? 'on' : ''}`}>
|
||||
<span>{getMessage('plan.mode.vertical.horizontal')}</span>
|
||||
<button onClick={() => setVerticalHorizontalMode(!verticalHorizontalMode)}>{verticalHorizontalMode ? 'ON' : 'OFF'}</button>
|
||||
<button
|
||||
title={`${getMessage('plan.mode.vertical.horizontal')} ${verticalHorizontalMode ? 'ON' : 'OFF'}`}
|
||||
onClick={() => setVerticalHorizontalMode(!verticalHorizontalMode)}
|
||||
>
|
||||
{verticalHorizontalMode ? 'ON' : 'OFF'}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
<div className="btn-from">
|
||||
<button className={`btn01 ${commonUtils.text ? 'active' : ''}`} onClick={() => commonFunctions('text')}></button>
|
||||
<button className={`btn02 ${commonUtils.dimension ? 'active' : ''} `} onClick={() => commonFunctions('dimension')}></button>
|
||||
<button className={`btn03 ${commonUtils.distance ? 'active' : ''} `} onClick={() => commonFunctions('distance')}></button>
|
||||
<button className={`btn01 ${commonUtils.text ? 'active' : ''}`} onClick={() => commonFunctions('text')} title="文字作成"></button>
|
||||
<button
|
||||
className={`btn02 ${commonUtils.dimension ? 'active' : ''} `}
|
||||
onClick={() => commonFunctions('dimension')}
|
||||
title="寸法作成"
|
||||
></button>
|
||||
<button
|
||||
className={`btn03 ${commonUtils.distance ? 'active' : ''} `}
|
||||
onClick={() => commonFunctions('distance')}
|
||||
title="定規"
|
||||
></button>
|
||||
</div>
|
||||
{isObjectNotEmpty(selectedRoofMaterial) && addedRoofs.length > 0 && (
|
||||
<div className="select-box">
|
||||
@ -580,6 +612,7 @@ export default function CanvasMenu(props) {
|
||||
sourceKey={'index'}
|
||||
targetKey={'index'}
|
||||
disabled={+basicSetting.roofSizeSet === 3}
|
||||
tagTitle={'屋根材変更'}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
@ -588,9 +621,10 @@ export default function CanvasMenu(props) {
|
||||
<button
|
||||
className={`btn10 ${floorPlanState.refFileModalOpen && 'active'}`}
|
||||
onClick={() => setFloorPlanState({ ...floorPlanState, refFileModalOpen: true })}
|
||||
title="読込"
|
||||
></button>
|
||||
{/*<button className="btn04" onClick={() => setShowCanvasSettingModal(true)}></button>*/}
|
||||
<button className="btn04" onClick={handlePopup}></button>
|
||||
<button className="btn04" onClick={handlePopup} title="設定"></button>
|
||||
</div>
|
||||
<div className="size-control">
|
||||
<button
|
||||
@ -599,7 +633,9 @@ export default function CanvasMenu(props) {
|
||||
handleZoom(false)
|
||||
}}
|
||||
></button>
|
||||
<span onClick={handleZoomClear}>{canvasZoom}%</span>
|
||||
<span onClick={handleZoomClear} title="拡大・縮小">
|
||||
{canvasZoom}%
|
||||
</span>
|
||||
<button
|
||||
className="control-btn plus"
|
||||
onClick={() => {
|
||||
@ -608,8 +644,8 @@ export default function CanvasMenu(props) {
|
||||
></button>
|
||||
</div>
|
||||
<div className="btn-from">
|
||||
<button className="btn08" onClick={handleSaveCanvas}></button>
|
||||
<button className="btn09" onClick={handleLeaveCanvas}></button>
|
||||
<button className="btn08" onClick={handleSaveCanvas} title="保存"></button>
|
||||
<button className="btn09" onClick={handleLeaveCanvas} title="物件検索画面へ移動"></button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@ -634,7 +670,7 @@ export default function CanvasMenu(props) {
|
||||
onClick={() => setEstimatePopupOpen(true)}
|
||||
>
|
||||
<span className="ico ico01"></span>
|
||||
<span className="name">{getMessage('plan.menu.estimate.docDown')}</span>
|
||||
<span className="name">{getMessage('plan.menu.estimate.docDownload')}</span>
|
||||
</button>
|
||||
<button type="button" style={{ display: saveButtonStyle }} className="btn-frame gray ico-flx" onClick={handleEstimateSubmit}>
|
||||
<span className="ico ico02"></span>
|
||||
|
||||
@ -1,15 +1,18 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import { useContext, useEffect } from 'react'
|
||||
import CanvasMenu from '@/components/floor-plan/CanvasMenu'
|
||||
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
|
||||
import { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
|
||||
import { usePopup } from '@/hooks/usePopup'
|
||||
import '@/styles/contents.scss'
|
||||
import { notFound, useSearchParams } from 'next/navigation'
|
||||
import { useRecoilState, useResetRecoilState } from 'recoil'
|
||||
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
|
||||
import { correntObjectNoState } from '@/store/settingAtom'
|
||||
import { currentMenuState } from '@/store/canvasAtom'
|
||||
import { globalLocaleStore } from '@/store/localeAtom'
|
||||
import { useAxios } from '@/hooks/useAxios'
|
||||
import { GlobalDataContext } from '@/app/GlobalDataProvider'
|
||||
|
||||
export default function FloorPlan({ children }) {
|
||||
const [correntObjectNo, setCurrentObjectNo] = useRecoilState(correntObjectNoState)
|
||||
@ -20,12 +23,39 @@ export default function FloorPlan({ children }) {
|
||||
const { selectedMenu, setSelectedMenu } = useCanvasMenu()
|
||||
const { fetchSettings } = useCanvasSetting()
|
||||
const resetCurrentMenu = useResetRecoilState(currentMenuState)
|
||||
const globalLocaleState = useRecoilValue(globalLocaleStore)
|
||||
const { promiseGet } = useAxios(globalLocaleState)
|
||||
const { setManagementState } = useContext(GlobalDataContext)
|
||||
|
||||
useEffect(() => {
|
||||
getStuffDetailInfo()
|
||||
|
||||
return () => {
|
||||
resetCurrentMenu()
|
||||
}
|
||||
}, [])
|
||||
|
||||
const getStuffDetailInfo = () => {
|
||||
promiseGet({ url: `/api/object/${objectNo}/detail` }).then((res) => {
|
||||
if (res.status === 200) {
|
||||
const { data } = res
|
||||
console.log(data)
|
||||
|
||||
let surfaceTypeValue
|
||||
if (res.data.surfaceType === 'Ⅲ・Ⅳ') {
|
||||
surfaceTypeValue = '3'
|
||||
} else if (res.data.surfaceType === 'Ⅱ') {
|
||||
surfaceTypeValue = '2'
|
||||
}
|
||||
//설치높이 0이면 빈값으로로 셋팅
|
||||
if (res.data.installHeight === '0') {
|
||||
res.data.installHeight = ''
|
||||
}
|
||||
setManagementState({ ...res.data, surfaceTypeValue: surfaceTypeValue })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* URL 파라미터에서 objectNo 설정
|
||||
*/
|
||||
|
||||
@ -5,9 +5,10 @@ import { useEffect } from 'react'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import useMenu from '@/hooks/common/useMenu'
|
||||
import { canvasState, currentMenuState } from '@/store/canvasAtom'
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
|
||||
import { subMenusState } from '@/store/menuAtom'
|
||||
import { useCanvasMenu } from '@/hooks/common/useCanvasMenu'
|
||||
import { commonUtilsState } from '@/store/commonUtilsAtom'
|
||||
|
||||
export default function MenuDepth01() {
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
@ -16,8 +17,10 @@ export default function MenuDepth01() {
|
||||
const { selectedMenu, setSelectedMenu } = useCanvasMenu()
|
||||
const [currentMenu, setCurrentMenu] = useRecoilState(currentMenuState)
|
||||
const subMenus = useRecoilValue(subMenusState)
|
||||
const resetCommonUtils = useResetRecoilState(commonUtilsState)
|
||||
|
||||
const onClickMenu = ({ id, menu }) => {
|
||||
resetCommonUtils()
|
||||
if (menu === currentMenu) {
|
||||
handleMenu(selectedMenu)
|
||||
} else {
|
||||
|
||||
@ -120,7 +120,7 @@ export default function ImgLoad() {
|
||||
value={refImage ? (refImage?.name ?? '') : (currentCanvasPlan?.bgImageName ?? '')}
|
||||
readOnly
|
||||
/>
|
||||
{refImage && <button className="img-check" onClick={handleFileDelete}></button>}
|
||||
{currentCanvasPlan?.bgImageName && <button className="img-check" onClick={handleFileDelete}></button>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) * -1 : Number(horizonSize)) : 0,
|
||||
arrow1 ? (arrow1 === '↑' ? Number(verticalSize) * -1 : Number(verticalSize)) : 0,
|
||||
arrow2 ? (arrow2 === '←' ? (Number(normalizeDigits(horizonSize)) / 10) * -1 : Number(normalizeDigits(horizonSize)) / 10) : 0,
|
||||
arrow1 ? (arrow1 === '↑' ? (Number(normalizeDigits(verticalSize)) / 10) * -1 : Number(normalizeDigits(verticalSize)) / 10) : 0,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
move(
|
||||
currentObject,
|
||||
arrow2 ? (arrow2 === '←' ? Number(horizonSize) * -1 : Number(horizonSize)) : 0,
|
||||
arrow1 ? (arrow1 === '↑' ? Number(verticalSize) * -1 : Number(verticalSize)) : 0,
|
||||
arrow2 ? (arrow2 === '←' ? (Number(normalizeDigits(horizonSize)) / 10) * -1 : Number(normalizeDigits(horizonSize)) / 10) : 0,
|
||||
arrow1 ? (arrow1 === '↑' ? (Number(normalizeDigits(verticalSize)) / 10) * -1 : Number(normalizeDigits(verticalSize)) / 10) : 0,
|
||||
)
|
||||
}
|
||||
|
||||
@ -65,7 +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">
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -1,109 +1,88 @@
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { POLYGON_TYPE, MODULE_SETUP_TYPE } from '@/common/common'
|
||||
import WithDraggable from '@/components/common/draggable/WithDraggable'
|
||||
import { useContext, useEffect, useRef, useState } from 'react'
|
||||
import Module from '@/components/floor-plan/modal/basic/step/Module'
|
||||
import PitchModule from '@/components/floor-plan/modal/basic/step/pitch/PitchModule'
|
||||
import { Orientation } from '@/components/floor-plan/modal/basic/step/Orientation'
|
||||
import PitchPlacement from '@/components/floor-plan/modal/basic/step/pitch/PitchPlacement'
|
||||
import Placement from '@/components/floor-plan/modal/basic/step/Placement'
|
||||
import { useRecoilValue, useRecoilState } from 'recoil'
|
||||
import { canvasSettingState, canvasState, checkedModuleState, isManualModuleSetupState } from '@/store/canvasAtom'
|
||||
import { usePopup } from '@/hooks/usePopup'
|
||||
import { Orientation } from '@/components/floor-plan/modal/basic/step/Orientation'
|
||||
import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting'
|
||||
import { useEvent } from '@/hooks/useEvent'
|
||||
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
|
||||
import { addedRoofsState, corridorDimensionSelector, basicSettingState } from '@/store/settingAtom'
|
||||
import { isObjectNotEmpty } from '@/util/common-utils'
|
||||
import Swal from 'sweetalert2'
|
||||
import { useCanvasPopupStatusController } from '@/hooks/common/useCanvasPopupStatusController'
|
||||
import { useMasterController } from '@/hooks/common/useMasterController'
|
||||
import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting'
|
||||
import { useModuleSelection } from '@/hooks/module/useModuleSelection'
|
||||
import { useOrientation } from '@/hooks/module/useOrientation'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { usePopup } from '@/hooks/usePopup'
|
||||
import {
|
||||
canvasState,
|
||||
checkedModuleState,
|
||||
currentCanvasPlanState,
|
||||
isManualModuleLayoutSetupState,
|
||||
isManualModuleSetupState,
|
||||
toggleManualSetupModeState,
|
||||
} from '@/store/canvasAtom'
|
||||
import { loginUserStore } from '@/store/commonAtom'
|
||||
import { currentCanvasPlanState } from '@/store/canvasAtom'
|
||||
import { POLYGON_TYPE } from '@/common/common'
|
||||
import { roofsState } from '@/store/roofAtom'
|
||||
import { moduleSelectionDataState } from '@/store/selectedModuleOptions'
|
||||
import { addedRoofsState, basicSettingState } from '@/store/settingAtom'
|
||||
import { isObjectNotEmpty } from '@/util/common-utils'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
import Swal from 'sweetalert2'
|
||||
import Trestle from './step/Trestle'
|
||||
|
||||
export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
|
||||
const { getMessage } = useMessage()
|
||||
const { closePopup } = usePopup()
|
||||
const [tabNum, setTabNum] = useState(1)
|
||||
const canvasSetting = useRecoilValue(canvasSettingState)
|
||||
const orientationRef = useRef(null)
|
||||
const { initEvent } = useEvent()
|
||||
const [isManualModuleSetup, setIsManualModuleSetup] = useRecoilState(isManualModuleSetupState)
|
||||
const moduleSelectionData = useRecoilValue(moduleSelectionDataState)
|
||||
const addedRoofs = useRecoilValue(addedRoofsState)
|
||||
const [isManualModuleLayoutSetup, setIsManualModuleLayoutSetup] = useRecoilState(isManualModuleLayoutSetupState)
|
||||
const trestleRef = useRef(null)
|
||||
const [moduleSelectionData, setModuleSelectionData] = useRecoilState(moduleSelectionDataState)
|
||||
const [addedRoofs, setAddedRoofs] = useRecoilState(addedRoofsState)
|
||||
const loginUserState = useRecoilValue(loginUserStore)
|
||||
const currentCanvasPlan = useRecoilValue(currentCanvasPlanState)
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
const [basicSetting, setBasicSettings] = useRecoilState(basicSettingState)
|
||||
const [isClosePopup, setIsClosePopup] = useState({ close: false, id: 0 })
|
||||
const [checkedModules, setCheckedModules] = useRecoilState(checkedModuleState)
|
||||
const [roofs, setRoofs] = useState(addedRoofs)
|
||||
const [manualSetupMode, setManualSetupMode] = useRecoilState(toggleManualSetupModeState)
|
||||
const [layoutSetup, setLayoutSetup] = useState([{}])
|
||||
const {
|
||||
selectedModules,
|
||||
roughnessCodes,
|
||||
windSpeedCodes,
|
||||
managementState,
|
||||
setManagementState,
|
||||
moduleList,
|
||||
setSelectedModules,
|
||||
selectedSurfaceType,
|
||||
setSelectedSurfaceType,
|
||||
installHeight,
|
||||
setInstallHeight,
|
||||
standardWindSpeed,
|
||||
setStandardWindSpeed,
|
||||
verticalSnowCover,
|
||||
setVerticalSnowCover,
|
||||
handleChangeModule,
|
||||
handleChangeSurfaceType,
|
||||
handleChangeWindSpeed,
|
||||
handleChangeInstallHeight,
|
||||
handleChangeVerticalSnowCover,
|
||||
} = useModuleSelection({ addedRoofs })
|
||||
const { nextStep, compasDeg, setCompasDeg } = useOrientation()
|
||||
const { trigger: orientationTrigger } = useCanvasPopupStatusController(1)
|
||||
const { trigger: trestleTrigger } = useCanvasPopupStatusController(2)
|
||||
const { trigger: placementTrigger } = useCanvasPopupStatusController(3)
|
||||
const [roofsStore, setRoofsStore] = useRecoilState(roofsState)
|
||||
|
||||
// const { initEvent } = useContext(EventContext)
|
||||
const { manualModuleSetup, autoModuleSetup, manualFlatroofModuleSetup, autoFlatroofModuleSetup } = useModuleBasicSetting(tabNum)
|
||||
const { manualModuleSetup, autoModuleSetup, manualFlatroofModuleSetup, autoFlatroofModuleSetup, manualModuleLayoutSetup, restoreModuleInstArea } =
|
||||
useModuleBasicSetting(tabNum)
|
||||
const { updateObjectDate } = useMasterController()
|
||||
|
||||
const handleBtnNextStep = () => {
|
||||
if (tabNum === 1) {
|
||||
orientationRef.current.handleNextStep()
|
||||
} else if (tabNum === 2) {
|
||||
if (basicSetting.roofSizeSet !== '3') {
|
||||
if (!isObjectNotEmpty(moduleSelectionData.module)) {
|
||||
Swal.fire({
|
||||
title: getMessage('module.not.found'),
|
||||
icon: 'warning',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (addedRoofs.length !== moduleSelectionData.roofConstructions.length) {
|
||||
Swal.fire({
|
||||
title: getMessage('construction.length.difference'),
|
||||
icon: 'warning',
|
||||
})
|
||||
return
|
||||
}
|
||||
//물건정보 갱신일 수정
|
||||
updateObjectDataApi({
|
||||
objectNo: currentCanvasPlan.objectNo, //오브젝트_no
|
||||
standardWindSpeedId: moduleSelectionData.common.stdWindSpeed, //기준풍속코드
|
||||
verticalSnowCover: moduleSelectionData.common.stdSnowLd, //적설량
|
||||
surfaceType: moduleSelectionData.common.illuminationTpNm, //면조도구분
|
||||
installHeight: moduleSelectionData.common.instHt, //설치높이
|
||||
userId: loginUserState.userId, //작성자아아디
|
||||
})
|
||||
} else {
|
||||
if (!isObjectNotEmpty(moduleSelectionData.module)) {
|
||||
Swal.fire({
|
||||
title: getMessage('module.not.found'),
|
||||
icon: 'warning',
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setTabNum(tabNum + 1)
|
||||
}
|
||||
|
||||
const placementRef = {
|
||||
isChidori: useRef('false'),
|
||||
setupLocation: useRef('eaves'),
|
||||
isMaxSetup: useRef('false'),
|
||||
}
|
||||
|
||||
const placementFlatRef = {
|
||||
setupLocation: useRef('south'),
|
||||
}
|
||||
|
||||
const handleManualModuleSetup = () => {
|
||||
setIsManualModuleSetup(!isManualModuleSetup)
|
||||
}
|
||||
|
||||
const updateObjectDataApi = async (params) => {
|
||||
const res = await updateObjectDate(params)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const moduleTabNum = basicSetting.roofSizeSet != 3 ? 3 : 2
|
||||
|
||||
let hasModules = canvas
|
||||
.getObjects()
|
||||
.filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE)
|
||||
@ -111,23 +90,42 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
|
||||
|
||||
if (hasModules) {
|
||||
orientationRef.current.handleNextStep()
|
||||
setTabNum(3)
|
||||
setTabNum(moduleTabNum)
|
||||
}
|
||||
}, [])
|
||||
|
||||
//팝업 닫기 버튼 이벤트
|
||||
const handleClosePopup = (id) => {
|
||||
if (tabNum == 3) {
|
||||
if (isManualModuleSetup) {
|
||||
setIsManualModuleSetup(false)
|
||||
}
|
||||
useEffect(() => {
|
||||
if (roofsStore && addedRoofs) {
|
||||
setRoofs(
|
||||
addedRoofs.map((roof, index) => {
|
||||
return {
|
||||
...roof,
|
||||
...roofsStore[index]?.addRoof,
|
||||
construction: roofsStore[index]?.construction,
|
||||
trestle: roofsStore[index]?.trestle,
|
||||
trestleDetail: roofsStore[index]?.trestleDetail,
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
setModuleSelectionData({
|
||||
...moduleSelectionData,
|
||||
roofConstructions: roofsStore.map((roof) => {
|
||||
return {
|
||||
roofIndex: roof.roofIndex,
|
||||
addRoof: roof.addRoof,
|
||||
construction: roof.construction,
|
||||
trestle: roof.trestle,
|
||||
trestleDetail: roof.trestleDetail,
|
||||
}
|
||||
}),
|
||||
})
|
||||
}
|
||||
setIsClosePopup({ close: true, id: id })
|
||||
}
|
||||
}, [roofsStore, addedRoofs])
|
||||
|
||||
useEffect(() => {
|
||||
if (basicSetting.roofSizeSet !== '3') {
|
||||
manualModuleSetup(placementRef)
|
||||
manualModuleSetup()
|
||||
} else {
|
||||
manualFlatroofModuleSetup(placementFlatRef)
|
||||
}
|
||||
@ -140,55 +138,224 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) {
|
||||
setIsManualModuleSetup(false)
|
||||
}, [checkedModules])
|
||||
|
||||
useEffect(() => {
|
||||
if (basicSetting.roofSizeSet !== '3') {
|
||||
if (manualSetupMode.indexOf('manualSetup') > -1) {
|
||||
manualModuleSetup()
|
||||
} else if (manualSetupMode.indexOf('manualLayoutSetup') > -1) {
|
||||
manualModuleLayoutSetup(layoutSetup)
|
||||
} else if (manualSetupMode.indexOf('off') > -1) {
|
||||
manualModuleSetup()
|
||||
manualModuleLayoutSetup(layoutSetup)
|
||||
}
|
||||
} else {
|
||||
manualFlatroofModuleSetup(placementFlatRef)
|
||||
}
|
||||
|
||||
if (isClosePopup.close) {
|
||||
closePopup(isClosePopup.id)
|
||||
}
|
||||
}, [manualSetupMode, isClosePopup])
|
||||
|
||||
useEffect(() => {
|
||||
if (isManualModuleLayoutSetup) {
|
||||
manualModuleLayoutSetup(layoutSetup)
|
||||
}
|
||||
}, [layoutSetup])
|
||||
|
||||
useEffect(() => {
|
||||
setIsManualModuleSetup(false)
|
||||
setIsManualModuleLayoutSetup(false)
|
||||
setManualSetupMode(`off`)
|
||||
}, [checkedModules])
|
||||
|
||||
const handleBtnNextStep = () => {
|
||||
if (tabNum === 1) {
|
||||
orientationRef.current.handleNextStep()
|
||||
setAddedRoofs(roofs)
|
||||
// setTabNum(tabNum + 1)
|
||||
return
|
||||
} else if (tabNum === 2) {
|
||||
if (basicSetting.roofSizeSet !== '3') {
|
||||
// if (addedRoofs.length !== moduleSelectionData.roofConstructions.length) {
|
||||
// Swal.fire({
|
||||
// title: getMessage('construction.length.difference'),
|
||||
// icon: 'warning',
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
trestleRef.current.isComplete().then((res) => {
|
||||
if (!res) return
|
||||
})
|
||||
//물건정보 갱신일 수정
|
||||
} else {
|
||||
if (!isObjectNotEmpty(moduleSelectionData.module)) {
|
||||
Swal.fire({
|
||||
title: getMessage('module.not.found'),
|
||||
icon: 'warning',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
setTabNum(tabNum + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const placementFlatRef = {
|
||||
setupLocation: useRef('south'),
|
||||
}
|
||||
|
||||
const handleManualModuleSetup = () => {
|
||||
setManualSetupMode(`manualSetup_${!isManualModuleSetup}`)
|
||||
setIsManualModuleSetup(!isManualModuleSetup)
|
||||
}
|
||||
|
||||
const handleManualModuleLayoutSetup = () => {
|
||||
setManualSetupMode(`manualLayoutSetup_${!isManualModuleLayoutSetup}`)
|
||||
setIsManualModuleLayoutSetup(!isManualModuleLayoutSetup)
|
||||
}
|
||||
|
||||
const updateObjectDataApi = async (params) => {
|
||||
const res = await updateObjectDate(params)
|
||||
}
|
||||
|
||||
//팝업 닫기 버튼 이벤트
|
||||
const handleClosePopup = (id) => {
|
||||
if (tabNum == 3) {
|
||||
if (isManualModuleSetup) {
|
||||
setIsManualModuleSetup(false)
|
||||
}
|
||||
if (isManualModuleLayoutSetup) {
|
||||
setIsManualModuleLayoutSetup(false)
|
||||
}
|
||||
}
|
||||
setIsClosePopup({ close: true, id: id })
|
||||
}
|
||||
|
||||
const orientationProps = {
|
||||
roofs,
|
||||
setRoofs,
|
||||
tabNum,
|
||||
setTabNum,
|
||||
compasDeg, // 방위각
|
||||
setCompasDeg,
|
||||
selectedModules,
|
||||
moduleSelectionData,
|
||||
setModuleSelectionData,
|
||||
roughnessCodes, // 면조도 목록
|
||||
windSpeedCodes, // 기준풍속 목록
|
||||
managementState,
|
||||
setManagementState,
|
||||
moduleList, // 모듈 리스트
|
||||
setSelectedModules,
|
||||
selectedSurfaceType,
|
||||
setSelectedSurfaceType,
|
||||
installHeight, // 설치높이
|
||||
setInstallHeight,
|
||||
standardWindSpeed, // 기준풍속
|
||||
setStandardWindSpeed,
|
||||
verticalSnowCover, // 적설량
|
||||
setVerticalSnowCover,
|
||||
currentCanvasPlan,
|
||||
loginUserState,
|
||||
handleChangeModule,
|
||||
handleChangeSurfaceType,
|
||||
handleChangeWindSpeed,
|
||||
handleChangeInstallHeight,
|
||||
handleChangeVerticalSnowCover,
|
||||
orientationTrigger,
|
||||
nextStep,
|
||||
updateObjectDataApi,
|
||||
}
|
||||
const trestleProps = {
|
||||
roofs,
|
||||
setRoofs,
|
||||
setRoofsStore,
|
||||
tabNum,
|
||||
setTabNum,
|
||||
moduleSelectionData,
|
||||
setModuleSelectionData,
|
||||
trestleTrigger,
|
||||
}
|
||||
const placementProps = {}
|
||||
|
||||
return (
|
||||
<WithDraggable isShow={true} pos={pos} className="lx-2">
|
||||
<WithDraggable isShow={true} pos={pos} className={basicSetting.roofSizeSet && basicSetting.roofSizeSet != '3' ? 'll' : 'lx-2'}>
|
||||
<WithDraggable.Header title={getMessage('plan.menu.module.circuit.setting.default')} onClose={() => handleClosePopup(id)} />
|
||||
<WithDraggable.Body>
|
||||
<div className="roof-module-tab">
|
||||
<div className={`module-tab-bx act`}>{getMessage('modal.module.basic.setting.orientation.setting')}</div>
|
||||
<span className={`tab-arr ${tabNum !== 1 ? 'act' : ''}`}></span>
|
||||
<div className={`module-tab-bx ${tabNum !== 1 ? 'act' : ''}`}>{getMessage('modal.module.basic.setting.module.setting')}</div>
|
||||
<span className={`tab-arr ${tabNum === 3 ? 'act' : ''}`}></span>
|
||||
<div className={`module-tab-bx ${tabNum === 3 ? 'act' : ''}`}>{getMessage('modal.module.basic.setting.module.placement')}</div>
|
||||
{basicSetting.roofSizeSet && basicSetting.roofSizeSet != '3' && (
|
||||
<>
|
||||
<div className={`module-tab-bx ${tabNum !== 1 ? 'act' : ''}`}>{getMessage('modal.module.basic.setting.module.setting')}</div>
|
||||
<span className={`tab-arr ${tabNum === 3 ? 'act' : ''}`}></span>
|
||||
<div className={`module-tab-bx ${tabNum === 3 ? 'act' : ''}`}>{getMessage('modal.module.basic.setting.module.placement')}</div>
|
||||
</>
|
||||
)}
|
||||
{basicSetting.roofSizeSet && basicSetting.roofSizeSet == '3' && (
|
||||
<>
|
||||
<div className={`module-tab-bx ${tabNum === 2 ? 'act' : ''}`}>{getMessage('modal.module.basic.setting.module.placement')}</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{tabNum === 1 && <Orientation ref={orientationRef} tabNum={tabNum} setTabNum={setTabNum} />}
|
||||
{tabNum === 1 && <Orientation ref={orientationRef} {...orientationProps} />}
|
||||
{/*배치면 초기설정 - 입력방법: 복시도 입력 || 실측값 입력*/}
|
||||
{basicSetting.roofSizeSet && basicSetting.roofSizeSet != '3' && tabNum === 2 && <Module setTabNum={setTabNum} />}
|
||||
{basicSetting.roofSizeSet && basicSetting.roofSizeSet != '3' && tabNum === 3 && <Placement setTabNum={setTabNum} ref={placementRef} />}
|
||||
|
||||
{basicSetting.roofSizeSet && basicSetting.roofSizeSet != '3' && tabNum === 2 && <Trestle ref={trestleRef} {...trestleProps} />}
|
||||
{basicSetting.roofSizeSet && basicSetting.roofSizeSet != '3' && tabNum === 3 && (
|
||||
<Placement setTabNum={setTabNum} layoutSetup={layoutSetup} setLayoutSetup={setLayoutSetup} />
|
||||
)}
|
||||
{/*배치면 초기설정 - 입력방법: 육지붕*/}
|
||||
{basicSetting.roofSizeSet && basicSetting.roofSizeSet == '3' && tabNum === 2 && <PitchModule setTabNum={setTabNum} />}
|
||||
{basicSetting.roofSizeSet && basicSetting.roofSizeSet == '3' && tabNum === 3 && (
|
||||
{/* {basicSetting.roofSizeSet && basicSetting.roofSizeSet == '3' && tabNum === 3 && <PitchModule setTabNum={setTabNum} />} */}
|
||||
{basicSetting.roofSizeSet && basicSetting.roofSizeSet == '3' && tabNum === 2 && (
|
||||
<PitchPlacement setTabNum={setTabNum} ref={placementFlatRef} />
|
||||
)}
|
||||
|
||||
<div className="grid-btn-wrap">
|
||||
{tabNum !== 1 && (
|
||||
<button className="btn-frame modal mr5" onClick={() => setTabNum(tabNum - 1)}>
|
||||
{getMessage('modal.module.basic.setting.prev')}
|
||||
</button>
|
||||
)}
|
||||
{/*{tabNum !== 3 && <button className="btn-frame modal act mr5">{getMessage('modal.common.save')}</button>}*/}
|
||||
{tabNum !== 3 && (
|
||||
<button className="btn-frame modal" onClick={handleBtnNextStep}>
|
||||
Next
|
||||
</button>
|
||||
)}
|
||||
|
||||
{tabNum === 3 && (
|
||||
{/* {tabNum === 1 && <button className="btn-frame modal mr5">{getMessage('modal.common.save')}</button>} */}
|
||||
{basicSetting.roofSizeSet && basicSetting.roofSizeSet != '3' && (
|
||||
<>
|
||||
{basicSetting.roofSizeSet && basicSetting.roofSizeSet != '3' && (
|
||||
{tabNum !== 1 && (
|
||||
<button className="btn-frame modal mr5" onClick={() => setTabNum(tabNum - 1)}>
|
||||
{getMessage('modal.module.basic.setting.prev')}
|
||||
</button>
|
||||
)}
|
||||
{tabNum !== 3 && (
|
||||
<button className="btn-frame modal" onClick={handleBtnNextStep}>
|
||||
Next
|
||||
</button>
|
||||
)}
|
||||
{tabNum === 3 && (
|
||||
<>
|
||||
<button className={`btn-frame modal mr5 ${isManualModuleLayoutSetup ? 'act' : ''}`} onClick={handleManualModuleLayoutSetup}>
|
||||
{getMessage('modal.module.basic.setting.row.batch')}
|
||||
</button>
|
||||
<button className="btn-frame modal mr5" onClick={() => autoModuleSetup(MODULE_SETUP_TYPE.LAYOUT, layoutSetup)}>
|
||||
{getMessage('modal.module.basic.setting.auto.row.batch')}
|
||||
</button>
|
||||
<button className={`btn-frame modal mr5 ${isManualModuleSetup ? 'act' : ''}`} onClick={handleManualModuleSetup}>
|
||||
{getMessage('modal.module.basic.setting.passivity.placement')}
|
||||
</button>
|
||||
<button className="btn-frame modal act" onClick={() => autoModuleSetup(placementRef)}>
|
||||
<button className="btn-frame modal act mr5" onClick={() => autoModuleSetup(MODULE_SETUP_TYPE.AUTO)}>
|
||||
{getMessage('modal.module.basic.setting.auto.placement')}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{basicSetting.roofSizeSet && basicSetting.roofSizeSet == '3' && (
|
||||
</>
|
||||
)}
|
||||
{basicSetting.roofSizeSet && basicSetting.roofSizeSet == '3' && (
|
||||
<>
|
||||
{tabNum === 1 && (
|
||||
<button className="btn-frame modal" onClick={handleBtnNextStep}>
|
||||
Next
|
||||
</button>
|
||||
)}
|
||||
{tabNum === 2 && (
|
||||
<>
|
||||
<button className="btn-frame modal mr5" onClick={() => setTabNum(tabNum - 1)}>
|
||||
{getMessage('modal.module.basic.setting.prev')}
|
||||
</button>
|
||||
<button className={`btn-frame modal mr5 ${isManualModuleSetup ? 'act' : ''}`} onClick={handleManualModuleSetup}>
|
||||
{getMessage('modal.module.basic.setting.passivity.placement')}
|
||||
</button>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -1,115 +1,606 @@
|
||||
import { forwardRef, useContext, useEffect, useImperativeHandle, useState } from 'react'
|
||||
import { forwardRef, use, useContext, useEffect, useImperativeHandle, useState } from 'react'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { useOrientation } from '@/hooks/module/useOrientation'
|
||||
import { getDegreeInOrientation } from '@/util/canvas-util'
|
||||
import { numberCheck } from '@/util/common-utils'
|
||||
import { useCanvasPopupStatusController } from '@/hooks/common/useCanvasPopupStatusController'
|
||||
import { addedRoofsState, basicSettingState } from '@/store/settingAtom'
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
|
||||
import QSelectBox from '@/components/common/select/QSelectBox'
|
||||
import { roofsState } from '@/store/roofAtom'
|
||||
import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting'
|
||||
import { useCommonCode } from '@/hooks/common/useCommonCode'
|
||||
import Swal from 'sweetalert2'
|
||||
import { normalizeDecimal} from '@/util/input-utils'
|
||||
|
||||
export const Orientation = forwardRef(({ tabNum }, ref) => {
|
||||
export const Orientation = forwardRef((props, ref) => {
|
||||
const { getMessage } = useMessage()
|
||||
|
||||
const { trigger: canvasPopupStatusTrigger } = useCanvasPopupStatusController(1)
|
||||
|
||||
const { nextStep, compasDeg, setCompasDeg } = useOrientation()
|
||||
|
||||
const { findCommonCode } = useCommonCode()
|
||||
const [hasAnglePassivity, setHasAnglePassivity] = useState(false)
|
||||
const basicSetting = useRecoilValue(basicSettingState)
|
||||
const [addedRoofs, setAddedRoofs] = useRecoilState(addedRoofsState) //지붕재 선택
|
||||
const [roofsStore, setRoofsStore] = useRecoilState(roofsState)
|
||||
const [roofTab, setRoofTab] = useState(0) //지붕재 탭
|
||||
const [selectedModuleSeries, setSelectedModuleSeries] = useState(null)
|
||||
const [moduleSeriesList, setModuleSeriesList] = useState([])
|
||||
const [filteredModuleList, setFilteredModuleList] = useState([])
|
||||
const {
|
||||
roofs,
|
||||
setRoofs,
|
||||
tabNum,
|
||||
setTabNum,
|
||||
compasDeg,
|
||||
setCompasDeg,
|
||||
selectedModules,
|
||||
roughnessCodes,
|
||||
windSpeedCodes,
|
||||
managementState,
|
||||
setManagementState,
|
||||
moduleList,
|
||||
moduleSelectionData,
|
||||
setModuleSelectionData,
|
||||
setSelectedModules,
|
||||
selectedSurfaceType,
|
||||
setSelectedSurfaceType,
|
||||
installHeight,
|
||||
setInstallHeight,
|
||||
standardWindSpeed,
|
||||
setStandardWindSpeed,
|
||||
verticalSnowCover,
|
||||
setVerticalSnowCover,
|
||||
orientationTrigger,
|
||||
nextStep,
|
||||
currentCanvasPlan,
|
||||
loginUserState,
|
||||
updateObjectDataApi,
|
||||
} = props
|
||||
const [inputCompasDeg, setInputCompasDeg] = useState(compasDeg ?? 0)
|
||||
const [inputInstallHeight, setInputInstallHeight] = useState('0')
|
||||
const [inputMargin, setInputMargin] = useState('0')
|
||||
const [inputVerticalSnowCover, setInputVerticalSnowCover] = useState('0')
|
||||
const [inputRoughness, setInputRoughness] = useState(selectedSurfaceType)
|
||||
const [inputStandardWindSpeed, setInputStandardWindSpeed] = useState(standardWindSpeed)
|
||||
const { restoreModuleInstArea } = useModuleBasicSetting()
|
||||
const moduleData = {
|
||||
header: [
|
||||
{ name: getMessage('module'), width: 150, prop: 'module', type: 'color-box' },
|
||||
{
|
||||
name: `${getMessage('height')} (mm)`,
|
||||
prop: 'height',
|
||||
},
|
||||
{ name: `${getMessage('width')} (mm)`, prop: 'width' },
|
||||
{ name: `${getMessage('output')} (W)`, prop: 'output' },
|
||||
],
|
||||
}
|
||||
|
||||
const allOption = {
|
||||
moduleSerCd: 'ALL',
|
||||
moduleSerNm: getMessage("board.sub.total") || 'ALL'
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (basicSetting.roofSizeSet === '3') {
|
||||
restoreModuleInstArea()
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (moduleSelectionData?.common) {
|
||||
setInputMargin(moduleSelectionData?.common?.margin)
|
||||
}
|
||||
}, [moduleSelectionData])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedModules) {
|
||||
const foundModule = moduleList.find((module) => module.itemId === selectedModules.itemId)
|
||||
if (foundModule) {
|
||||
setSelectedModules(foundModule)
|
||||
|
||||
// 선택된 모듈의 시리즈로 업데이트 (시리즈 목록이 있는 경우에만)
|
||||
if (moduleSeriesList.length > 0 && foundModule.moduleSerCd) {
|
||||
const currentSeries = moduleSeriesList.find(series => series.moduleSerCd === foundModule.moduleSerCd)
|
||||
if (currentSeries && (!selectedModuleSeries || selectedModuleSeries.moduleSerCd !== currentSeries.moduleSerCd)) {
|
||||
setSelectedModuleSeries(currentSeries)
|
||||
}
|
||||
}else{
|
||||
setSelectedModuleSeries(allOption)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [selectedModules, moduleList, moduleSeriesList])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedSurfaceType) {
|
||||
setInputRoughness(roughnessCodes.find((code) => code.clCode === managementState?.surfaceTypeValue))
|
||||
}
|
||||
}, [selectedSurfaceType])
|
||||
|
||||
useEffect(() => {
|
||||
if (standardWindSpeed) setInputStandardWindSpeed(windSpeedCodes.find((code) => code.clCode === managementState?.standardWindSpeedId))
|
||||
}, [standardWindSpeed])
|
||||
|
||||
useEffect(() => {
|
||||
if (managementState?.installHeight && managementState?.installHeight) {
|
||||
setSelectedSurfaceType(roughnessCodes.find((code) => code.clCode === managementState?.surfaceTypeValue))
|
||||
setInputInstallHeight(managementState?.installHeight)
|
||||
setStandardWindSpeed(windSpeedCodes.find((code) => code.clCode === managementState?.standardWindSpeedId))
|
||||
setInputVerticalSnowCover(managementState?.verticalSnowCover)
|
||||
}
|
||||
}, [managementState])
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleNextStep,
|
||||
}))
|
||||
|
||||
const handleNextStep = () => {
|
||||
nextStep()
|
||||
canvasPopupStatusTrigger(compasDeg)
|
||||
}
|
||||
if (isComplete()) {
|
||||
const common = {
|
||||
illuminationTp: inputRoughness.clCode,
|
||||
illuminationTpNm: inputRoughness.clCodeNm,
|
||||
instHt: inputInstallHeight,
|
||||
stdWindSpeed: inputStandardWindSpeed?.clCode,
|
||||
stdSnowLd: inputVerticalSnowCover,
|
||||
saleStoreNorthFlg: managementState?.saleStoreNorthFlg,
|
||||
moduleTpCd: selectedModules.itemTp,
|
||||
moduleItemId: selectedModules.itemId,
|
||||
margin: inputMargin,
|
||||
}
|
||||
setCompasDeg(inputCompasDeg)
|
||||
setInstallHeight(inputInstallHeight)
|
||||
setVerticalSnowCover(inputVerticalSnowCover)
|
||||
setSelectedSurfaceType(inputRoughness)
|
||||
setStandardWindSpeed(inputStandardWindSpeed)
|
||||
nextStep(inputCompasDeg)
|
||||
setManagementState({
|
||||
...managementState,
|
||||
installHeight: inputInstallHeight,
|
||||
verticalSnowCover: inputVerticalSnowCover,
|
||||
standardWindSpeedId: inputStandardWindSpeed?.clCode,
|
||||
surfaceType: inputRoughness.clCodeNm,
|
||||
surfaceTypeValue: inputRoughness.clCode,
|
||||
})
|
||||
setModuleSelectionData({
|
||||
...moduleSelectionData,
|
||||
module: {
|
||||
...selectedModules,
|
||||
},
|
||||
common,
|
||||
})
|
||||
orientationTrigger({
|
||||
compasDeg: inputCompasDeg,
|
||||
common: common,
|
||||
module: {
|
||||
...selectedModules,
|
||||
},
|
||||
margin: inputMargin,
|
||||
})
|
||||
updateObjectDataApi({
|
||||
objectNo: currentCanvasPlan.objectNo, //오브젝트_no
|
||||
standardWindSpeedId: inputStandardWindSpeed?.clCode, //기준풍속코드
|
||||
verticalSnowCover: inputVerticalSnowCover, //적설량
|
||||
surfaceType: inputRoughness.clCodeNm, //면조도구분
|
||||
installHeight: inputInstallHeight, //설치높이
|
||||
userId: loginUserState.userId, //작성자아아디
|
||||
})
|
||||
setTabNum(2)
|
||||
} else {
|
||||
if (!selectedModules || !selectedModules.itemId) {
|
||||
Swal.fire({
|
||||
title: getMessage('module.not.found'),
|
||||
icon: 'warning',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
checkDegree(compasDeg)
|
||||
}, [compasDeg])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const checkDegree = (e) => {
|
||||
if (e === '-0' || e === '-') {
|
||||
setCompasDeg('-')
|
||||
setInputCompasDeg('-')
|
||||
return
|
||||
}
|
||||
if (e === '0-') {
|
||||
setCompasDeg('-0')
|
||||
setInputCompasDeg('-0')
|
||||
return
|
||||
}
|
||||
if (Number(e) >= -180 && Number(e) <= 180) {
|
||||
if (numberCheck(Number(e))) {
|
||||
setCompasDeg(Number(e))
|
||||
const n = Number(normalizeDecimal(e))
|
||||
if (n >= -180 && n <= 180) {
|
||||
if (numberCheck(n)) {
|
||||
setInputCompasDeg(n)
|
||||
}
|
||||
} else {
|
||||
setCompasDeg(compasDeg)
|
||||
setInputCompasDeg(compasDeg)
|
||||
}
|
||||
}
|
||||
|
||||
const isComplete = () => {
|
||||
if (!selectedModules || !selectedModules.itemId) return false
|
||||
if (basicSetting && basicSetting.roofSizeSet !== '3') {
|
||||
if (inputInstallHeight <= 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (+inputVerticalSnowCover <= 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!inputStandardWindSpeed) return false
|
||||
if (!inputRoughness) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const handleChangeModuleSeries = (e) => {
|
||||
resetRoofs()
|
||||
setSelectedModuleSeries(e)
|
||||
|
||||
// 선택된 시리즈에 맞는 모듈 목록 필터링 및 첫 번째 모듈 선택
|
||||
if (e && moduleList.length > 0) {
|
||||
let filtered
|
||||
|
||||
if (e.moduleSerCd === 'ALL') {
|
||||
// "전체" 선택 시 모든 모듈 표시
|
||||
filtered = moduleList
|
||||
} else {
|
||||
// 특정 시리즈 선택 시 해당 시리즈 모듈만 표시
|
||||
//filtered = moduleList.filter(module => module.moduleSerCd === e.moduleSerCd)
|
||||
filtered = moduleList.filter(module => module && module.moduleSerCd && module.moduleSerCd === e.moduleSerCd)
|
||||
}
|
||||
|
||||
setFilteredModuleList(filtered)
|
||||
|
||||
// 필터링된 목록의 첫 번째 모듈을 자동 선택
|
||||
if (filtered.length > 0) {
|
||||
const firstModule = filtered[0]
|
||||
setSelectedModules(firstModule)
|
||||
// 상위 컴포넌트의 handleChangeModule 호출
|
||||
if (handleChangeModule) {
|
||||
handleChangeModule(firstModule)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 모듈 리스트가 비어있는 경우
|
||||
setFilteredModuleList([])
|
||||
setSelectedModules(null)
|
||||
}
|
||||
}
|
||||
|
||||
const handleChangeModule = (e) => {
|
||||
resetRoofs()
|
||||
setSelectedModules(e)
|
||||
}
|
||||
|
||||
const handleChangeRoughness = (e) => {
|
||||
resetRoofs()
|
||||
setInputRoughness(e)
|
||||
}
|
||||
|
||||
const handleChangeInstallHeight = (e) => {
|
||||
resetRoofs()
|
||||
setInputInstallHeight(e)
|
||||
}
|
||||
|
||||
const handleChangeStandardWindSpeed = (e) => {
|
||||
resetRoofs()
|
||||
setInputStandardWindSpeed(e)
|
||||
}
|
||||
|
||||
const handleChangeVerticalSnowCover = (e) => {
|
||||
resetRoofs()
|
||||
setInputVerticalSnowCover(e)
|
||||
}
|
||||
|
||||
const resetRoofs = () => {
|
||||
const newRoofs = addedRoofs.map((roof) => {
|
||||
return {
|
||||
...roof,
|
||||
trestle: {
|
||||
lengthBase: null,
|
||||
trestleMkrCd: null,
|
||||
constMthdCd: null,
|
||||
constTp: null,
|
||||
roofBaseCd: null,
|
||||
roofPchBase: null,
|
||||
},
|
||||
addRoof: {
|
||||
...roof.addRoof,
|
||||
lengthBase: null,
|
||||
eavesMargin: null,
|
||||
kerabaMargin: null,
|
||||
ridgeMargin: null,
|
||||
},
|
||||
construction: {
|
||||
constTp: null,
|
||||
cvrYn: 'N',
|
||||
snowGdPossYn: 'N',
|
||||
cvrChecked: false,
|
||||
snowGdChecked: false,
|
||||
},
|
||||
}
|
||||
})
|
||||
// setRoofs(newRoofs)
|
||||
// setAddedRoofs(newRoofs)
|
||||
setRoofsStore(newRoofs)
|
||||
}
|
||||
|
||||
// 모듈시리즈 목록 생성 및 commonCode와 매핑
|
||||
useEffect(() => {
|
||||
if (moduleList.length > 0 && moduleSeriesList.length === 0) {
|
||||
const moduleSeriesCodes = findCommonCode(207100) || []
|
||||
|
||||
// moduleList에서 고유한 moduleSerCd 추출
|
||||
const uniqueSeriesCd = [...new Set(moduleList.map(module => module.moduleSerCd).filter(Boolean))]
|
||||
|
||||
if (uniqueSeriesCd.length > 0) {
|
||||
// moduleSerCd와 commonCode를 매핑하여 기본 moduleSeriesList 생성
|
||||
const mappedSeries = uniqueSeriesCd.map(serCd => {
|
||||
const matchedCode = moduleSeriesCodes.find(code => code.clCode === serCd)
|
||||
return {
|
||||
moduleSerCd: serCd,
|
||||
moduleSerNm: matchedCode ? matchedCode.clCodeNm : serCd
|
||||
}
|
||||
})
|
||||
|
||||
// "전체" 옵션을 맨 앞에 추가
|
||||
const seriesList = [allOption, ...mappedSeries]
|
||||
setModuleSeriesList(seriesList)
|
||||
|
||||
// 현재 선택된 모듈이 있으면 해당 모듈의 시리즈를 찾아서 선택
|
||||
if (selectedModules && selectedModules.moduleSerCd) {
|
||||
const currentSeries = seriesList.find(series => series.moduleSerCd === selectedModules.moduleSerCd)
|
||||
if (currentSeries) {
|
||||
setSelectedModuleSeries(currentSeries)
|
||||
} else {
|
||||
setSelectedModuleSeries(allOption)
|
||||
// "ALL"이 선택되면 자동으로 모듈 필터링 및 선택 실행
|
||||
setTimeout(() => handleChangeModuleSeries(allOption), 0)
|
||||
}
|
||||
} else {
|
||||
// 선택된 모듈이 없으면 "전체"를 기본 선택
|
||||
setSelectedModuleSeries(allOption)
|
||||
// "ALL"이 선택되면 자동으로 모듈 필터링 및 선택 실행
|
||||
setTimeout(() => handleChangeModuleSeries(allOption), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [moduleList, selectedModules])
|
||||
|
||||
// 초기 로딩 시에만 필터링된 모듈 목록 설정
|
||||
useEffect(() => {
|
||||
if (moduleList.length > 0 && filteredModuleList.length === 0 && selectedModuleSeries) {
|
||||
let filtered
|
||||
|
||||
if (selectedModuleSeries.moduleSerCd === 'ALL') {
|
||||
// "전체" 선택 시 모든 모듈 표시
|
||||
filtered = moduleList
|
||||
} else {
|
||||
// 특정 시리즈 선택 시 해당 시리즈 모듈만 표시
|
||||
filtered = moduleList.filter(module => module.moduleSerCd === selectedModuleSeries.moduleSerCd)
|
||||
}
|
||||
|
||||
setFilteredModuleList(filtered)
|
||||
|
||||
if (filtered.length > 0 && !selectedModules) {
|
||||
setSelectedModules(filtered[0])
|
||||
}
|
||||
} else if (moduleList.length === 0 && filteredModuleList.length === 0 && selectedModuleSeries) {
|
||||
// 모듈 리스트가 비어있는 경우 빈 배열로 설정
|
||||
setFilteredModuleList([])
|
||||
}
|
||||
}, [moduleList, selectedModuleSeries]);
|
||||
return (
|
||||
<>
|
||||
<div className="properties-setting-wrap">
|
||||
<div className="outline-wrap">
|
||||
<div className="guide">{getMessage('modal.module.basic.setting.orientation.setting.info')}</div>
|
||||
<div className="roof-module-compas">
|
||||
<div className="compas-box">
|
||||
<div className="compas-box-inner">
|
||||
{Array.from({ length: 180 / 15 }).map((dot, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`circle ${getDegreeInOrientation(compasDeg) === -1 * (-15 * index + 180) || (index === 0 && compasDeg >= 172 && index === 0 && compasDeg <= 180) || (compasDeg === -180 && index === 0) ? 'act' : ''}`}
|
||||
onClick={() => {
|
||||
if (index === 0) {
|
||||
setCompasDeg(180)
|
||||
return
|
||||
}
|
||||
setCompasDeg(-1 * (-15 * index + 180))
|
||||
}}
|
||||
>
|
||||
{index === 0 && <i>180°</i>}
|
||||
{index === 6 && <i>-90°</i>}
|
||||
<div className="roof-module-inner">
|
||||
<div className="compas-wrapper">
|
||||
<div className="guide">{getMessage('modal.module.basic.setting.orientation.setting.info')}</div>
|
||||
<div className="roof-module-compas">
|
||||
<div className="compas-box">
|
||||
<div className="compas-box-inner">
|
||||
{Array.from({ length: 180 / 15 }).map((dot, index) => (
|
||||
<div
|
||||
key={index}
|
||||
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(-15 * index + 180)
|
||||
}}
|
||||
>
|
||||
{index === 0 && <i>180°</i>}
|
||||
{index === 6 && <i>90°</i>}
|
||||
</div>
|
||||
))}
|
||||
{Array.from({ length: 180 / 15 }).map((dot, index) => (
|
||||
<div
|
||||
key={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>}
|
||||
</div>
|
||||
))}
|
||||
<div className="compas">
|
||||
<div className="compas-arr" style={{ transform: `rotate(${-1 * getDegreeInOrientation(inputCompasDeg)}deg)` }}></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{Array.from({ length: 180 / 15 }).map((dot, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`circle ${compasDeg !== 180 && getDegreeInOrientation(compasDeg) === 15 * index ? 'act' : ''}`}
|
||||
onClick={() => setCompasDeg(15 * index)}
|
||||
>
|
||||
{index === 0 && <i>0°</i>}
|
||||
{index === 6 && <i>90°</i>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="center-wrap">
|
||||
<div className="outline-form">
|
||||
<div className="d-check-box pop mr10">
|
||||
<input type="checkbox" id="ch99" checked={hasAnglePassivity} onChange={() => setHasAnglePassivity(!hasAnglePassivity)} />
|
||||
<label htmlFor="ch99">{getMessage('modal.module.basic.setting.orientation.setting.angle.passivity')}</label>
|
||||
</div>
|
||||
))}
|
||||
<div className="compas">
|
||||
<div className="compas-arr" style={{ transform: `rotate(${getDegreeInOrientation(compasDeg)}deg)` }}></div>
|
||||
<div className="input-grid mr10" style={{ width: '60px' }}>
|
||||
<input
|
||||
type="text"
|
||||
className="input-origin block"
|
||||
value={inputCompasDeg}
|
||||
readOnly={!hasAnglePassivity}
|
||||
placeholder={0}
|
||||
onChange={(e) => checkDegree(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<span className="thin">°</span>
|
||||
<span className="thin">( -180 〜 180 )</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="center-wrap">
|
||||
<div className="d-check-box pop">
|
||||
<input type="checkbox" id="ch99" checked={hasAnglePassivity} onChange={() => setHasAnglePassivity(!hasAnglePassivity)} />
|
||||
<label htmlFor="ch99">{getMessage('modal.module.basic.setting.orientation.setting.angle.passivity')}(-180 〜 180)</label>
|
||||
</div>
|
||||
<div className="outline-form">
|
||||
<div className="input-grid mr10" style={{ width: '160px' }}>
|
||||
<input
|
||||
type="text"
|
||||
className="input-origin block"
|
||||
value={compasDeg}
|
||||
readOnly={!hasAnglePassivity}
|
||||
placeholder={0}
|
||||
onChange={
|
||||
(e) => checkDegree(e.target.value)
|
||||
// setCompasDeg(
|
||||
|
||||
// e.target.value === '-' || (e.target.value !== '' && parseInt(e.target.value) <= 180 && parseInt(e.target.value) >= -180)
|
||||
// ? e.target.value
|
||||
// : 0,
|
||||
// )
|
||||
}
|
||||
/>
|
||||
<div className="compas-table-wrap">
|
||||
<div className="compas-table-box mb10">
|
||||
<div className="outline-form mb10">
|
||||
<span>{getMessage('modal.module.basic.setting.module.series.setting')}</span>
|
||||
<div className="grid-select">
|
||||
<div className="grid-select">
|
||||
<QSelectBox
|
||||
options={moduleSeriesList.length > 0 ? moduleSeriesList : [allOption]}
|
||||
value={selectedModuleSeries}
|
||||
targetKey={'moduleSerCd'}
|
||||
sourceKey={'moduleSerCd'}
|
||||
showKey={'moduleSerNm'}
|
||||
onChange={(e) => handleChangeModuleSeries(e)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="outline-form mb10">
|
||||
<span>{getMessage('modal.module.basic.setting.module.setting2')}</span>
|
||||
<div className="grid-select">
|
||||
{filteredModuleList && (
|
||||
<QSelectBox
|
||||
options={filteredModuleList}
|
||||
value={selectedModules}
|
||||
targetKey={'itemId'}
|
||||
sourceKey={'itemId'}
|
||||
showKey={'itemNm'}
|
||||
onChange={(e) => handleChangeModule(e)}
|
||||
showFirstOptionWhenEmpty = {true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="roof-module-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{moduleData.header.map((header) => {
|
||||
return (
|
||||
<th key={header.prop} style={{ width: header.width ? header.width + 'px' : '' }}>
|
||||
{header.name}
|
||||
</th>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Array.from({ length: 3 }).map((_, index) => {
|
||||
return selectedModules && selectedModules?.itemList && selectedModules?.itemList?.length >= index + 1 ? (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
<div className="color-wrap">
|
||||
<span
|
||||
className="color-box"
|
||||
style={{
|
||||
backgroundColor: selectedModules.itemList[index].color,
|
||||
}}
|
||||
></span>
|
||||
<span className="name">{selectedModules.itemList[index].itemNm}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="al-r">{Number(selectedModules.itemList[index].shortAxis).toFixed(0)}</td>
|
||||
<td className="al-r">{Number(selectedModules.itemList[index].longAxis).toFixed(0)}</td>
|
||||
<td className="al-r">{Number(selectedModules.itemList[index].wpOut).toFixed(0)}</td>
|
||||
</tr>
|
||||
) : (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
<div className="color-wrap"></div>
|
||||
</td>
|
||||
<td className="al-r"></td>
|
||||
<td className="al-r"></td>
|
||||
<td className="al-r"></td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{basicSetting && basicSetting.roofSizeSet === '3' && (
|
||||
<div className="outline-form mt15">
|
||||
<span>{getMessage('modal.module.basic.setting.module.placement.area')}</span>
|
||||
<div className="input-grid mr10" style={{ width: '60px' }}>
|
||||
<input type="text" className="input-origin block" value={inputMargin} onChange={(e) => setInputMargin(normalizeDecimal(e.target.value))} />
|
||||
</div>
|
||||
<span className="thin">m</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span className="thin">°</span>
|
||||
|
||||
{basicSetting && basicSetting.roofSizeSet !== '3' && (
|
||||
<div className="compas-table-box">
|
||||
<div className="compas-grid-table">
|
||||
<div className="outline-form">
|
||||
<span>{getMessage('modal.module.basic.setting.module.surface.type')}</span>
|
||||
<div className="grid-select">
|
||||
{roughnessCodes.length > 0 && managementState && (
|
||||
<QSelectBox
|
||||
options={roughnessCodes}
|
||||
value={inputRoughness}
|
||||
targetKey={'clCode'}
|
||||
sourceKey={'clCode'}
|
||||
showKey={'clCodeNm'}
|
||||
onChange={(e) => handleChangeRoughness(e)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="outline-form">
|
||||
<span>{getMessage('modal.module.basic.setting.module.fitting.height')}</span>
|
||||
<div className="input-grid mr10">
|
||||
<input
|
||||
type="text"
|
||||
className="input-origin block"
|
||||
value={inputInstallHeight}
|
||||
onChange={(e) => handleChangeInstallHeight(normalizeDecimal(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<span className="thin">m</span>
|
||||
</div>
|
||||
<div className="outline-form">
|
||||
<span>{getMessage('modal.module.basic.setting.module.standard.wind.speed')}</span>
|
||||
<div className="grid-select">
|
||||
{windSpeedCodes.length > 0 && managementState && (
|
||||
<QSelectBox
|
||||
title={''}
|
||||
options={windSpeedCodes}
|
||||
value={inputStandardWindSpeed}
|
||||
targetKey={'clCode'}
|
||||
sourceKey={'clCode'}
|
||||
showKey={'clCodeNm'}
|
||||
onChange={(e) => handleChangeStandardWindSpeed(e)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="outline-form">
|
||||
<span>{getMessage('modal.module.basic.setting.module.standard.snowfall.amount')}</span>
|
||||
<div className="input-grid mr10">
|
||||
<input
|
||||
type="text"
|
||||
className="input-origin block"
|
||||
value={inputVerticalSnowCover}
|
||||
onChange={(e) => handleChangeVerticalSnowCover(normalizeDecimal(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<span className="thin">cm</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,29 +1,46 @@
|
||||
import { forwardRef, useEffect, useState } from 'react'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting'
|
||||
import { checkedModuleState, currentCanvasPlanState, isManualModuleSetupState } from '@/store/canvasAtom'
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
|
||||
import {
|
||||
checkedModuleState,
|
||||
isManualModuleLayoutSetupState,
|
||||
isManualModuleSetupState,
|
||||
moduleRowColArrayState,
|
||||
moduleSetupOptionState,
|
||||
toggleManualSetupModeState,
|
||||
} from '@/store/canvasAtom'
|
||||
import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'
|
||||
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
|
||||
import { isObjectNotEmpty } from '@/util/common-utils'
|
||||
import { normalizeDigits } from '@/util/input-utils'
|
||||
import Image from 'next/image'
|
||||
|
||||
const Placement = forwardRef((props, refs) => {
|
||||
const { getMessage } = useMessage()
|
||||
const [isChidori, setIsChidori] = useState(false)
|
||||
const [useTab, setUseTab] = useState(true)
|
||||
const [guideType, setGuideType] = useState('batch')
|
||||
|
||||
const [isChidoriNotAble, setIsChidoriNotAble] = useState(false)
|
||||
|
||||
const [setupLocation, setSetupLocation] = useState('eaves')
|
||||
const [isMaxSetup, setIsMaxSetup] = useState('false')
|
||||
const [selectedItems, setSelectedItems] = useState({})
|
||||
|
||||
const [selectedModules, setSelectedModules] = useRecoilState(selectedModuleState)
|
||||
|
||||
const setCheckedModules = useSetRecoilState(checkedModuleState)
|
||||
const moduleSelectionData = useRecoilValue(moduleSelectionDataState)
|
||||
const { makeModuleInitArea } = useModuleBasicSetting(3)
|
||||
const { makeModuleInitArea, roofOutlineColor } = useModuleBasicSetting(3)
|
||||
|
||||
const [isMultiModule, setIsMultiModule] = useState(false)
|
||||
|
||||
const [isManualModuleSetup, setIsManualModuleSetup] = useRecoilState(isManualModuleSetupState)
|
||||
//언마운트시 버튼 초기화
|
||||
const setIsManualModuleSetup = useSetRecoilState(isManualModuleSetupState)
|
||||
const setIsManualModuleLayoutSetup = useSetRecoilState(isManualModuleLayoutSetupState)
|
||||
const setManualSetupMode = useSetRecoilState(toggleManualSetupModeState)
|
||||
|
||||
const [moduleSetupOption, setModuleSetupOption] = useRecoilState(moduleSetupOptionState) //모듈 설치 옵션
|
||||
const resetModuleSetupOption = useResetRecoilState(moduleSetupOptionState)
|
||||
|
||||
const [colspan, setColspan] = useState(1)
|
||||
const moduleRowColArray = useRecoilValue(moduleRowColArrayState)
|
||||
|
||||
//모듈 배치면 생성
|
||||
useEffect(() => {
|
||||
@ -36,11 +53,24 @@ const Placement = forwardRef((props, refs) => {
|
||||
makeModuleInitArea(moduleSelectionData)
|
||||
}
|
||||
|
||||
if (moduleSelectionData.module.itemList.length > 1) {
|
||||
setColspan(2)
|
||||
}
|
||||
|
||||
return () => {
|
||||
// refs.isChidori.current = 'false'
|
||||
// refs.setupLocation.current = 'eaves'
|
||||
setIsManualModuleSetup(false)
|
||||
setIsManualModuleLayoutSetup(false)
|
||||
setManualSetupMode('off')
|
||||
resetModuleSetupOption()
|
||||
}
|
||||
}, [])
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log('moduleRowColArray', moduleRowColArray)
|
||||
// }, [moduleRowColArray])
|
||||
|
||||
//최초 지입시 체크
|
||||
useEffect(() => {
|
||||
if (isObjectNotEmpty(moduleSelectionData)) {
|
||||
@ -54,8 +84,10 @@ const Placement = forwardRef((props, refs) => {
|
||||
initCheckedModule = { ...initCheckedModule, [obj.itemId]: true }
|
||||
}
|
||||
})
|
||||
|
||||
setSelectedItems(initCheckedModule)
|
||||
setSelectedModules(moduleSelectionData.module)
|
||||
props.setLayoutSetup(moduleSelectionData.module.itemList.map((item) => ({ moduleId: item.itemId, col: 0, row: 0, checked: true })))
|
||||
}
|
||||
|
||||
//모듈 배치면 생성
|
||||
@ -80,63 +112,81 @@ const Placement = forwardRef((props, refs) => {
|
||||
header: [
|
||||
{ type: 'check', name: '', prop: 'check', width: 70 },
|
||||
{ type: 'color-box', name: getMessage('module'), prop: 'module' },
|
||||
{ type: 'text', name: `${getMessage('output')} (W)`, prop: 'output', width: 70 },
|
||||
{ type: 'text', name: getMessage('modal.module.basic.setting.module.placement.mix.asg.yn'), prop: 'mixAsgYn', width: 50 },
|
||||
{ type: 'text', name: `段数`, prop: 'rows', width: 60 },
|
||||
{ type: 'text', name: `列数`, prop: 'cols', width: 60 },
|
||||
],
|
||||
rows: [],
|
||||
}
|
||||
|
||||
const handleChangeChidori = (e) => {
|
||||
const bool = e.target.value === 'true' ? true : false
|
||||
setIsChidori(bool)
|
||||
refs.isChidori.current = e.target.value
|
||||
setModuleSetupOption({ ...moduleSetupOption, isChidori: bool })
|
||||
|
||||
//변경하면 수동 다 꺼짐
|
||||
setIsManualModuleSetup(false)
|
||||
setIsManualModuleLayoutSetup(false)
|
||||
setManualSetupMode('off')
|
||||
}
|
||||
|
||||
const handleSetupLocation = (e) => {
|
||||
setSetupLocation(e.target.value)
|
||||
refs.setupLocation.current = e.target.value
|
||||
}
|
||||
setModuleSetupOption({ ...moduleSetupOption, setupLocation: e.target.value })
|
||||
|
||||
const handleMaxSetup = (e) => {
|
||||
if (e.target.checked) {
|
||||
setIsMaxSetup('true')
|
||||
refs.isMaxSetup.current = 'true'
|
||||
} else {
|
||||
setIsMaxSetup('false')
|
||||
refs.isMaxSetup.current = 'false'
|
||||
}
|
||||
//변경하면 수동 다 꺼짐
|
||||
setIsManualModuleSetup(false)
|
||||
setIsManualModuleLayoutSetup(false)
|
||||
setManualSetupMode('off')
|
||||
}
|
||||
|
||||
//체크된 모듈 아이디 추출
|
||||
const handleSelectedItem = (e) => {
|
||||
const handleSelectedItem = (e, itemId) => {
|
||||
setSelectedItems({ ...selectedItems, [e.target.name]: e.target.checked })
|
||||
|
||||
const newLayoutSetup = [...props.layoutSetup]
|
||||
props.layoutSetup.forEach((item, index) => {
|
||||
if (item.moduleId === itemId) {
|
||||
newLayoutSetup[index] = { ...props.layoutSetup[index], checked: e.target.checked }
|
||||
}
|
||||
})
|
||||
props.setLayoutSetup(newLayoutSetup)
|
||||
}
|
||||
|
||||
const handleLayoutSetup = (e, itemId, index) => {
|
||||
const newLayoutSetup = [...props.layoutSetup]
|
||||
newLayoutSetup[index] = {
|
||||
...newLayoutSetup[index],
|
||||
moduleId: itemId,
|
||||
[e.target.name]: Number(normalizeDigits(e.target.value)),
|
||||
}
|
||||
props.setLayoutSetup(newLayoutSetup)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="module-table-flex-wrap mb10">
|
||||
<div className="module-table-flex-wrap">
|
||||
<div className="module-table-box">
|
||||
<div className="module-table-inner">
|
||||
<div className="roof-module-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{moduleData.header.map((data) => (
|
||||
<th key={data.prop} style={{ width: data.width ? data.width : '' }}>
|
||||
{data.type === 'check' ? (
|
||||
<div className="d-check-box no-text pop">
|
||||
<input type="checkbox" id="ch01" disabled />
|
||||
<label htmlFor="ch01"></label>
|
||||
</div>
|
||||
) : (
|
||||
data.name
|
||||
)}
|
||||
</th>
|
||||
))}
|
||||
<tr>
|
||||
{moduleData.header.map((data) => (
|
||||
<th key={data.prop} style={{ width: data.width ? data.width : '' }}>
|
||||
{data.type === 'check' ? (
|
||||
<div className="d-check-box no-text pop">
|
||||
<input type="checkbox" id="ch01" disabled />
|
||||
<label htmlFor="ch01"></label>
|
||||
</div>
|
||||
) : (
|
||||
data.name
|
||||
)}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{selectedModules.itemList &&
|
||||
selectedModules.itemList.map((item, index) => (
|
||||
{selectedModules?.itemList &&
|
||||
selectedModules?.itemList?.map((item, index) => (
|
||||
<tr key={index}>
|
||||
<td className="al-c">
|
||||
<div className="d-check-box no-text pop">
|
||||
@ -145,7 +195,7 @@ const Placement = forwardRef((props, refs) => {
|
||||
id={item.itemId}
|
||||
name={item.itemId}
|
||||
checked={selectedItems[item.itemId]}
|
||||
onChange={handleSelectedItem}
|
||||
onChange={(e) => handleSelectedItem(e, item.itemId)}
|
||||
/>
|
||||
<label htmlFor={item.itemId}></label>
|
||||
</div>
|
||||
@ -156,93 +206,209 @@ const Placement = forwardRef((props, refs) => {
|
||||
<span className="name">{item.itemNm}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="al-r">{item.wpOut}</td>
|
||||
<td className="al-c">
|
||||
<div className="color-wrap">
|
||||
<span className="name">{item.mixAsgYn}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="al-r">
|
||||
<div className="input-grid">
|
||||
<input
|
||||
type="text"
|
||||
className="input-origin block"
|
||||
name="row"
|
||||
value={props.layoutSetup[index]?.row ?? 1}
|
||||
//defaultValue={0}
|
||||
onChange={(e) => handleLayoutSetup(e, item.itemId, index)}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td className="al-r">
|
||||
<div className="input-grid">
|
||||
<input
|
||||
type="text"
|
||||
className="input-origin block"
|
||||
name="col"
|
||||
value={props.layoutSetup[index]?.col ?? 1}
|
||||
//defaultValue={0}
|
||||
onChange={(e) => handleLayoutSetup(e, item.itemId, index)}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="module-table-box">
|
||||
<div className="module-table-box non-flex">
|
||||
<div className="module-table-inner">
|
||||
<div className="self-table-tit">{getMessage('modal.module.basic.setting.module.placement.select.fitting.type')}</div>
|
||||
<div className="module-self-table">
|
||||
<div className="self-table-item">
|
||||
<div className="self-item-th">{getMessage('modal.module.basic.setting.module.placement.waterfowl.arrangement')}</div>
|
||||
<div className="self-item-td">
|
||||
<div className="pop-form-radio">
|
||||
<div className="d-check-radio pop">
|
||||
<input
|
||||
type="radio"
|
||||
name="radio01"
|
||||
id="ra01"
|
||||
checked={isChidori}
|
||||
disabled={isChidoriNotAble}
|
||||
value={'true'}
|
||||
onChange={(e) => handleChangeChidori(e)}
|
||||
/>
|
||||
<label htmlFor="ra01">{getMessage('modal.module.basic.setting.module.placement.do')}</label>
|
||||
</div>
|
||||
<div className="d-check-radio pop">
|
||||
<input type="radio" name="radio02" id="ra02" checked={!isChidori} value={'false'} onChange={(e) => handleChangeChidori(e)} />
|
||||
<label htmlFor="ra02">{getMessage('modal.module.basic.setting.module.placement.do.not')}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="self-table-item">
|
||||
<div className="self-item-th">{getMessage('modal.module.basic.setting.module.placement.arrangement.standard')}</div>
|
||||
<div className="self-item-td">
|
||||
<div className="pop-form-radio">
|
||||
<div className="d-check-radio pop">
|
||||
<input
|
||||
type="radio"
|
||||
name="radio03"
|
||||
id="ra03"
|
||||
checked={setupLocation === 'center'}
|
||||
value={'center'}
|
||||
onChange={handleSetupLocation}
|
||||
disabled={isMultiModule}
|
||||
/>
|
||||
<label htmlFor="ra03">{getMessage('modal.module.basic.setting.module.placement.arrangement.standard.center')}</label>
|
||||
</div>
|
||||
<div className="d-check-radio pop">
|
||||
<input
|
||||
type="radio"
|
||||
name="radio04"
|
||||
id="ra04"
|
||||
checked={setupLocation === 'eaves'}
|
||||
value={'eaves'}
|
||||
onChange={handleSetupLocation}
|
||||
/>
|
||||
<label htmlFor="ra04">{getMessage('modal.module.basic.setting.module.placement.arrangement.standard.eaves')}</label>
|
||||
</div>
|
||||
<div className="d-check-radio pop">
|
||||
<input
|
||||
type="radio"
|
||||
name="radio05"
|
||||
id="ra05"
|
||||
checked={setupLocation === 'ridge'}
|
||||
value={'ridge'}
|
||||
onChange={handleSetupLocation}
|
||||
disabled={isMultiModule}
|
||||
/>
|
||||
<label htmlFor="ra05">{getMessage('modal.module.basic.setting.module.placement.arrangement.standard.ridge')}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="self-table-flx">
|
||||
{/* <div className="d-check-box pop">
|
||||
<input type="checkbox" id="ch04" checked={isMaxSetup === 'true'} value={'true'} onChange={handleMaxSetup} />
|
||||
<label htmlFor="ch04">{getMessage('modal.module.basic.setting.module.placement.maximum')}</label>
|
||||
</div> */}
|
||||
<div className="roof-module-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{getMessage('modal.module.basic.setting.module.placement.waterfowl.arrangement')}</th>
|
||||
<th>{getMessage('modal.module.basic.setting.module.placement.arrangement.standard')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div className="hexagonal-radio-wrap">
|
||||
<div className="d-check-radio pop mb10">
|
||||
<input
|
||||
type="radio"
|
||||
name="radio02"
|
||||
id="ra03"
|
||||
checked={moduleSetupOption.isChidori}
|
||||
disabled={isChidoriNotAble}
|
||||
value={'true'}
|
||||
onChange={(e) => handleChangeChidori(e)}
|
||||
/>
|
||||
<label htmlFor="ra03">{getMessage('modal.module.basic.setting.module.placement.do')}</label>
|
||||
</div>
|
||||
<div className="d-check-radio pop">
|
||||
<input
|
||||
type="radio"
|
||||
name="radio02"
|
||||
id="ra04"
|
||||
checked={!moduleSetupOption.isChidori}
|
||||
value={'false'}
|
||||
onChange={(e) => handleChangeChidori(e)}
|
||||
/>
|
||||
<label htmlFor="ra04">{getMessage('modal.module.basic.setting.module.placement.do.not')}</label>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="hexagonal-radio-wrap">
|
||||
<div className="d-check-radio pop mb10">
|
||||
<input
|
||||
type="radio"
|
||||
name="radio03"
|
||||
id="ra05"
|
||||
checked={moduleSetupOption.setupLocation === 'eaves'}
|
||||
value={'eaves'}
|
||||
onChange={handleSetupLocation}
|
||||
/>
|
||||
<label htmlFor="ra05">{getMessage('modal.module.basic.setting.module.placement.arrangement.standard.eaves')}</label>
|
||||
</div>
|
||||
<div className="d-check-radio pop">
|
||||
<input
|
||||
type="radio"
|
||||
name="radio03"
|
||||
id="ra06"
|
||||
checked={moduleSetupOption.setupLocation === 'ridge'}
|
||||
value={'ridge'}
|
||||
onChange={handleSetupLocation}
|
||||
disabled={isMultiModule}
|
||||
/>
|
||||
<label htmlFor="ra06">{getMessage('modal.module.basic.setting.module.placement.arrangement.standard.ridge')}</label>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hide-tab-wrap">
|
||||
<div className="hide-check-guide">
|
||||
{getMessage('modal.module.basic.setting.module.placement.info')}
|
||||
<button className={`arr ${useTab ? 'act' : ''}`} onClick={() => setUseTab(!useTab)}></button>
|
||||
</div>
|
||||
<div className={`hide-tab-contents ${!useTab ? 'hide' : ''}`}>
|
||||
<div className="roof-content-tab-wrap">
|
||||
<button className={`btn-frame block modal mr5 ${guideType === 'batch' ? 'act' : ''} `} onClick={() => setGuideType('batch')}>
|
||||
{getMessage('modal.module.basic.setting.module.placement.info.batch')}
|
||||
</button>
|
||||
<button className={`btn-frame block modal mr5 ${guideType === 'module' ? 'act' : ''}`} onClick={() => setGuideType('module')}>
|
||||
{getMessage('modal.module.basic.setting.module.placement.info.module')}
|
||||
</button>
|
||||
</div>
|
||||
{guideType === 'batch' && (
|
||||
<div className={`roof-warning-wrap mt10`}>
|
||||
<div className="guide">
|
||||
{getMessage('modal.module.basic.setting.module.placement.info.batch.content1')}
|
||||
<br />
|
||||
{getMessage('modal.module.basic.setting.module.placement.info.batch.content2')}
|
||||
</div>
|
||||
<div className="roof-warning-img-wrap">
|
||||
<div className="roof-warning-img">
|
||||
<Image src={'/static/images/canvas/roof_warning_correct.png'} width={350} height={198} alt="" />
|
||||
</div>
|
||||
<div className="roof-warning-img">
|
||||
<Image src={'/static/images/canvas/roof_warning_wrong.png'} width={350} height={198} alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{guideType === 'module' && (
|
||||
<div className={`module-table-box mt10 ${!useTab ? 'hide' : ''}`}>
|
||||
<div className="module-table-inner">
|
||||
<div className="roof-module-table">
|
||||
<table className="">
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowSpan={2} style={{ width: '22%' }}></th>
|
||||
{selectedModules &&
|
||||
selectedModules.itemList?.map((item) => (
|
||||
// <th colSpan={colspan}>
|
||||
<th>
|
||||
<div className="color-wrap">
|
||||
<span className="color-box" style={{ backgroundColor: item.color }}></span>
|
||||
<span className="name">{item.itemNm}</span>
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
{colspan > 1 && <th rowSpan={2}>{getMessage('modal.module.basic.setting.module.placement.max.rows.multiple')}</th>}
|
||||
</tr>
|
||||
<tr>
|
||||
{selectedModules &&
|
||||
selectedModules.itemList?.map((item) => (
|
||||
<>
|
||||
<th>{getMessage('modal.module.basic.setting.module.placement.max.row')}</th>
|
||||
{/* {colspan > 1 && <th>{getMessage('modal.module.basic.setting.module.placement.max.rows.multiple')}</th>} */}
|
||||
</>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{moduleSelectionData.roofConstructions.map((item, index) => (
|
||||
<tr>
|
||||
<td>
|
||||
<div className="color-wrap">
|
||||
<span className="color-box" style={{ backgroundColor: roofOutlineColor(item.addRoof?.index) }}></span>
|
||||
<span className="name">{item.addRoof?.roofMatlNmJp}</span>
|
||||
</div>
|
||||
</td>
|
||||
{moduleRowColArray[index]?.map((item, index2) => (
|
||||
<>
|
||||
<td className="al-c">{item.moduleMaxRows}</td>
|
||||
{/* {colspan > 1 && <td className="al-c">{item.mixModuleMaxRows}</td>} */}
|
||||
{colspan > 1 && index2 === moduleRowColArray[index].length - 1 && <td className="al-c">{item.maxRow}</td>}
|
||||
</>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
962
src/components/floor-plan/modal/basic/step/Trestle.jsx
Normal file
@ -0,0 +1,962 @@
|
||||
import { GlobalDataContext } from '@/app/GlobalDataProvider'
|
||||
import QSelectBox from '@/components/common/select/QSelectBox'
|
||||
import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting'
|
||||
import { useModuleTrestle } from '@/hooks/module/useModuleTrestle'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { currentAngleTypeSelector, pitchTextSelector } from '@/store/canvasAtom'
|
||||
import { roofsState } from '@/store/roofAtom'
|
||||
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
|
||||
import { forwardRef, useContext, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
import Swal from 'sweetalert2'
|
||||
import { normalizeDigits } from '@/util/input-utils'
|
||||
|
||||
const Trestle = forwardRef((props, ref) => {
|
||||
const { tabNum, setTabNum, trestleTrigger, roofs, setRoofs, moduleSelectionData, setModuleSelectionData, setRoofsStore } = props
|
||||
const { getMessage } = useMessage()
|
||||
// const [selectedTrestle, setSelectedTrestle] = useState()
|
||||
const currentAngleType = useRecoilValue(currentAngleTypeSelector)
|
||||
const pitchText = useRecoilValue(pitchTextSelector)
|
||||
const [selectedRoof, setSelectedRoof] = useState(null)
|
||||
const [isAutoSelecting, setIsAutoSelecting] = useState(false) // 자동 선택 중인지 상태
|
||||
const [autoSelectTimeout, setAutoSelectTimeout] = useState(null) // 타임아웃 참조
|
||||
const autoSelectTimeoutRef = useRef(null)
|
||||
|
||||
// 공통 타임아웃 설정 (밀리초)
|
||||
const AUTO_SELECT_TIMEOUT = 500 // API 호출 완료 대기 시간
|
||||
const {
|
||||
trestleState,
|
||||
trestleDetail,
|
||||
dispatch,
|
||||
raftBaseList,
|
||||
trestleList,
|
||||
constMthdList,
|
||||
roofBaseList,
|
||||
constructionList,
|
||||
eavesMargin,
|
||||
ridgeMargin,
|
||||
kerabaMargin,
|
||||
setEavesMargin,
|
||||
setRidgeMargin,
|
||||
setKerabaMargin,
|
||||
lengthBase,
|
||||
setLengthBase,
|
||||
hajebichi,
|
||||
setHajebichi,
|
||||
cvrYn,
|
||||
cvrChecked,
|
||||
snowGdPossYn,
|
||||
snowGdChecked,
|
||||
setCvrYn,
|
||||
setCvrChecked,
|
||||
setSnowGdPossYn,
|
||||
setSnowGdChecked,
|
||||
} = useModuleTrestle({
|
||||
selectedRoof,
|
||||
})
|
||||
const selectedModules = useRecoilValue(selectedModuleState) //선택된 모듈
|
||||
// const [moduleSelectionData, setModuleSelectionData] = useRecoilState(moduleSelectionDataState)
|
||||
const [selectedRaftBase, setSelectedRaftBase] = useState(null)
|
||||
const [selectedTrestle, setSelectedTrestle] = useState(null)
|
||||
const [selectedConstMthd, setSelectedConstMthd] = useState(null)
|
||||
const [selectedConstruction, setSelectedConstruction] = useState(null)
|
||||
const [selectedRoofBase, setSelectedRoofBase] = useState(null)
|
||||
const { managementState } = useContext(GlobalDataContext)
|
||||
const { restoreModuleInstArea } = useModuleBasicSetting()
|
||||
const [flag, setFlag] = useState(false)
|
||||
const tempModuleSelectionData = useRef(null)
|
||||
const [autoSelectStep, setAutoSelectStep] = useState(null) // 'raftBase', 'trestle', 'constMthd', 'roofBase', 'construction'
|
||||
const prevHajebichiRef = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
if (roofs && roofs.length > 0 && !selectedRoof) {
|
||||
console.log("roofs:::::", roofs.length)
|
||||
setLengthBase(roofs[0].length);
|
||||
setSelectedRoof(roofs[0])
|
||||
}
|
||||
if (selectedRoof && selectedRoof.lenAuth === "C") {
|
||||
onChangeLength(selectedRoof.length);
|
||||
}else if (selectedRoof && ["C", "R"].includes(selectedRoof.raftAuth) && roofs && roofs.length > 0) {
|
||||
onChangeRaftBase(roofs[0]);
|
||||
}else if (selectedRoof && ["C", "R"].includes(selectedRoof.roofPchAuth) && roofs && roofs.length > 0 &&
|
||||
roofs[0].hajebichi !== prevHajebichiRef.current ) {
|
||||
prevHajebichiRef.current = roofs[0].hajebichi;
|
||||
onChangeHajebichi(roofs[0].hajebichi);
|
||||
}
|
||||
|
||||
//모듈 설치 영역 복구
|
||||
restoreModuleInstArea()
|
||||
}, [roofs, selectedRoof]) // selectedRoof 추가
|
||||
|
||||
useEffect(() => {
|
||||
if (flag && moduleSelectionData) {
|
||||
if (JSON.stringify(tempModuleSelectionData.current) === JSON.stringify(moduleSelectionData)) {
|
||||
setTabNum(tabNum + 1)
|
||||
}
|
||||
}
|
||||
}, [flag, moduleSelectionData])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedRoof) {
|
||||
if (moduleSelectionData?.roofConstructions?.length >= selectedRoof.index + 1) {
|
||||
const { construction, trestle, trestleDetail } = moduleSelectionData?.roofConstructions[selectedRoof.index]
|
||||
dispatch({
|
||||
type: 'SET_INITIALIZE',
|
||||
roof: { common: moduleSelectionData.common, module: moduleSelectionData.module, construction, trestle, trestleDetail, ...selectedRoof },
|
||||
})
|
||||
} else {
|
||||
dispatch({ type: 'SET_INITIALIZE', roof: { ...selectedRoof, common: moduleSelectionData.common, module: moduleSelectionData.module } })
|
||||
}
|
||||
}
|
||||
}, [selectedRoof])
|
||||
|
||||
useEffect(() => {
|
||||
if (raftBaseList.length > 0) {
|
||||
setSelectedRaftBase(raftBaseList.find((raft) => raft.clCode === selectedRoof?.raft) ?? null)
|
||||
} else {
|
||||
setSelectedRaftBase(null)
|
||||
}
|
||||
}, [raftBaseList])
|
||||
|
||||
useEffect(() => {
|
||||
if (trestleList.length > 0) {
|
||||
const existingTrestle = trestleList.find(
|
||||
(trestle) => trestle.trestleMkrCd === trestleState?.trestleMkrCd
|
||||
);
|
||||
if (existingTrestle) {
|
||||
setSelectedTrestle(existingTrestle)
|
||||
} else if (autoSelectStep === 'trestle') {
|
||||
// 자동 선택: 첫 번째 가대메이커 선택
|
||||
console.log('Auto selecting first trestle:', trestleList[0])
|
||||
const firstTrestle = trestleList[0]
|
||||
onChangeTrestleMaker(firstTrestle)
|
||||
// setAutoSelectStep은 onChangeTrestleMaker 내부에서 처리됨
|
||||
}
|
||||
} else {
|
||||
setSelectedTrestle(null)
|
||||
}
|
||||
}, [trestleList, autoSelectStep])
|
||||
|
||||
useEffect(() => {
|
||||
if (constMthdList.length > 0) {
|
||||
const existingConstMthd = constMthdList.find((constMthd) => constMthd.constMthdCd === trestleState?.constMthdCd)
|
||||
if (existingConstMthd) {
|
||||
setSelectedConstMthd(existingConstMthd)
|
||||
} else if (autoSelectStep === 'constMthd') {
|
||||
// 자동 선택: 첫 번째 공법 선택
|
||||
const firstConstMthd = constMthdList[0]
|
||||
onChangeConstMthd(firstConstMthd)
|
||||
setAutoSelectStep('roofBase') // 다음 단계로 설정
|
||||
}
|
||||
} else {
|
||||
setSelectedConstMthd(null)
|
||||
}
|
||||
}, [constMthdList, autoSelectStep])
|
||||
|
||||
useEffect(() => {
|
||||
if (roofBaseList.length > 0) {
|
||||
const existingRoofBase = roofBaseList.find((roofBase) => roofBase.roofBaseCd === trestleState?.roofBaseCd)
|
||||
if (existingRoofBase) {
|
||||
setSelectedRoofBase(existingRoofBase)
|
||||
} else if (autoSelectStep === 'roofBase') {
|
||||
// 자동 선택: 첫 번째 지붕밑바탕 선택
|
||||
const firstRoofBase = roofBaseList[0]
|
||||
onChangeRoofBase(firstRoofBase)
|
||||
setAutoSelectStep('construction') // 다음 단계로 설정
|
||||
}
|
||||
} else {
|
||||
setSelectedRoofBase(null)
|
||||
}
|
||||
}, [roofBaseList, autoSelectStep])
|
||||
|
||||
useEffect(() => {
|
||||
if (constructionList.length > 0) {
|
||||
const existingConstruction = constructionList.find((construction) => construction.constTp === trestleState.constTp)
|
||||
if (existingConstruction) {
|
||||
setSelectedConstruction(existingConstruction)
|
||||
} else if (autoSelectStep === 'construction') {
|
||||
// 자동 선택: 첫 번째 가능한 construction 선택
|
||||
const availableConstructions = constructionList.filter((construction) => construction.constPossYn === 'Y')
|
||||
if (availableConstructions.length > 0) {
|
||||
const firstConstruction = availableConstructions[0]
|
||||
const firstIndex = constructionList.findIndex((construction) => construction.constTp === firstConstruction.constTp)
|
||||
handleConstruction(firstIndex)
|
||||
setAutoSelectStep(null) // 자동 선택 완료
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: getMessage('modal.module.basic.settting.module.error4', [selectedRoof?.nameJp]),
|
||||
icon: 'warning',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (constructionList.filter((construction) => construction.constPossYn === 'Y').length === 0) {
|
||||
Swal.fire({
|
||||
title: getMessage('modal.module.basic.settting.module.error4', [selectedRoof?.nameJp]),
|
||||
icon: 'warning',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
setSelectedConstruction(null)
|
||||
}
|
||||
}, [constructionList, autoSelectStep])
|
||||
|
||||
const getConstructionState = (index) => {
|
||||
if (constructionList && constructionList.length > 0) {
|
||||
if (constructionList[index].constPossYn === 'Y') {
|
||||
if (trestleState && trestleState.constTp === constructionList[index].constTp) {
|
||||
return 'blue'
|
||||
}
|
||||
return 'white'
|
||||
}
|
||||
return 'no-click'
|
||||
}
|
||||
return 'no-click'
|
||||
}
|
||||
|
||||
const onChangeLength = (e) => {
|
||||
setLengthBase(e)
|
||||
// 다음 단계들 초기화
|
||||
setSelectedRaftBase(null)
|
||||
setSelectedTrestle(null)
|
||||
setSelectedConstMthd(null)
|
||||
setSelectedRoofBase(null)
|
||||
setSelectedConstruction(null)
|
||||
|
||||
dispatch({
|
||||
type: 'SET_LENGTH',
|
||||
roof: {
|
||||
length: e,
|
||||
moduleTpCd: selectedModules.itemTp ?? '',
|
||||
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
|
||||
raft: selectedRaftBase?.clCode,
|
||||
},
|
||||
})
|
||||
|
||||
// 자동으로 첫 번째 서까래 간격 선택
|
||||
if (raftBaseList.length > 0) {
|
||||
|
||||
const inx = raftBaseList.findIndex((raft) => raft.clCode === selectedRoof?.raft) ?? 0
|
||||
const firstRaftBase = raftBaseList[inx]
|
||||
onChangeRaftBase(firstRaftBase)
|
||||
}
|
||||
}
|
||||
|
||||
const onChangeRaftBase = (e) => {
|
||||
setSelectedRaftBase(e)
|
||||
// 다음 단계들 초기화
|
||||
setSelectedTrestle(null)
|
||||
setSelectedConstMthd(null)
|
||||
setSelectedRoofBase(null)
|
||||
setSelectedConstruction(null)
|
||||
|
||||
dispatch({
|
||||
type: 'SET_RAFT_BASE',
|
||||
roof: {
|
||||
moduleTpCd: selectedModules.itemTp ?? '',
|
||||
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
|
||||
raft: e.clCode,
|
||||
},
|
||||
})
|
||||
|
||||
// 다음 단계(가대메이커) 자동 선택 설정 - 지연 실행
|
||||
setTimeout(() => {
|
||||
setAutoSelectStep('trestle')
|
||||
}, AUTO_SELECT_TIMEOUT) // API 호출 완료를 위한 더 긴 지연
|
||||
}
|
||||
|
||||
const onChangeHajebichi = (e) => {
|
||||
setHajebichi(e)
|
||||
// 다음 단계들 초기화
|
||||
setSelectedTrestle(null)
|
||||
setSelectedConstMthd(null)
|
||||
setSelectedRoofBase(null)
|
||||
setSelectedConstruction(null)
|
||||
|
||||
// roofs 배열에서 selectedRoof.index와 같은 인덱스의 지붕 객체 업데이트
|
||||
if (selectedRoof && selectedRoof.index !== undefined) {
|
||||
const updatedRoofs = roofs.map((roof, index) => (index === selectedRoof.index ? { ...roof, hajebichi: Number(e) } : roof))
|
||||
setRoofs(updatedRoofs)
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: 'SET_HAJEBICHI',
|
||||
roof: {
|
||||
moduleTpCd: selectedModules.itemTp ?? '',
|
||||
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
|
||||
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
|
||||
hajebichi: e,
|
||||
},
|
||||
})
|
||||
|
||||
// 다음 단계(가대메이커) 자동 선택 설정 - 지연 실행
|
||||
setTimeout(() => {
|
||||
setAutoSelectStep('trestle')
|
||||
}, AUTO_SELECT_TIMEOUT)
|
||||
}
|
||||
|
||||
const onChangeTrestleMaker = (e) => {
|
||||
setSelectedTrestle(e)
|
||||
// 다음 단계들 초기화
|
||||
setSelectedConstMthd(null)
|
||||
setSelectedRoofBase(null)
|
||||
setSelectedConstruction(null)
|
||||
|
||||
dispatch({
|
||||
type: 'SET_TRESTLE_MAKER',
|
||||
roof: {
|
||||
moduleTpCd: selectedModules.itemTp ?? '',
|
||||
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
|
||||
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
|
||||
//hajebichi: selectedRaftBase?.hajebichi ?? selectedRoof?.hajebichi,
|
||||
trestleMkrCd: e.trestleMkrCd,
|
||||
},
|
||||
})
|
||||
|
||||
// API 호출 완료 후 다음 단계(공법) 자동 선택 설정
|
||||
setTimeout(() => {
|
||||
setAutoSelectStep('constMthd')
|
||||
}, AUTO_SELECT_TIMEOUT)
|
||||
}
|
||||
|
||||
const onChangeConstMthd = (e) => {
|
||||
setSelectedConstMthd(e)
|
||||
// 다음 단계 초기화
|
||||
setSelectedRoofBase(null)
|
||||
setSelectedConstruction(null)
|
||||
|
||||
dispatch({
|
||||
type: 'SET_CONST_MTHD',
|
||||
roof: {
|
||||
moduleTpCd: selectedModules.itemTp ?? '',
|
||||
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
|
||||
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
|
||||
//hajebichi: selectedRaftBase?.hajebichi ?? selectedRoof?.hajebichi,
|
||||
trestleMkrCd: selectedTrestle?.trestleMkrCd,
|
||||
constMthdCd: e.constMthdCd,
|
||||
},
|
||||
})
|
||||
|
||||
// 기존 타임아웃 취소
|
||||
if (autoSelectTimeoutRef.current) {
|
||||
clearTimeout(autoSelectTimeoutRef.current)
|
||||
}
|
||||
|
||||
// 자동 선택 중 상태 활성화
|
||||
setIsAutoSelecting(true)
|
||||
|
||||
// API 호출 완료 후 다음 단계(지붕밑바탕) 자동 선택 설정
|
||||
const timeoutId = setTimeout(() => {
|
||||
setAutoSelectStep('roofBase')
|
||||
setIsAutoSelecting(false)
|
||||
}, AUTO_SELECT_TIMEOUT)
|
||||
|
||||
autoSelectTimeoutRef.current = timeoutId
|
||||
}
|
||||
|
||||
const onChangeRoofBase = (e) => {
|
||||
setSelectedRoofBase(e)
|
||||
setSelectedConstruction(null)
|
||||
|
||||
dispatch({
|
||||
type: 'SET_ROOF_BASE',
|
||||
roof: {
|
||||
moduleTpCd: selectedModules.itemTp ?? '',
|
||||
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
|
||||
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
|
||||
//hajebichi: selectedRaftBase?.hajebichi ?? selectedRoof?.hajebichi,
|
||||
trestleMkrCd: selectedTrestle?.trestleMkrCd,
|
||||
constMthdCd: selectedConstMthd?.constMthdCd,
|
||||
roofBaseCd: e.roofBaseCd,
|
||||
illuminationTp: managementState?.surfaceTypeValue ?? '',
|
||||
instHt: managementState?.installHeight ?? '',
|
||||
stdWindSpeed: managementState?.standardWindSpeedId ?? '',
|
||||
stdSnowLd: managementState?.verticalSnowCover ?? '',
|
||||
inclCd: selectedRoof?.pitch ?? 0,
|
||||
roofPitch: Math.round(hajebichi ?? 0),
|
||||
},
|
||||
})
|
||||
|
||||
// API 호출 완료 후 다음 단계(construction) 자동 선택 설정
|
||||
setTimeout(() => {
|
||||
setAutoSelectStep('construction')
|
||||
}, AUTO_SELECT_TIMEOUT)
|
||||
}
|
||||
|
||||
const handleConstruction = (index) => {
|
||||
if (constructionList[index]?.constPossYn === 'Y') {
|
||||
dispatch({
|
||||
type: 'SET_CONSTRUCTION',
|
||||
roof: {
|
||||
moduleTpCd: selectedModules.itemTp ?? '',
|
||||
roofMatlCd: selectedRoof?.roofMatlCd ?? '',
|
||||
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
|
||||
//hajebichi: selectedRaftBase?.hajebichi ?? selectedRoof?.hajebichi,
|
||||
trestleMkrCd: selectedTrestle.trestleMkrCd,
|
||||
constMthdCd: selectedConstMthd.constMthdCd,
|
||||
roofBaseCd: selectedRoofBase.roofBaseCd,
|
||||
illuminationTp: managementState?.surfaceTypeValue ?? '',
|
||||
instHt: managementState?.installHeight ?? '',
|
||||
stdWindSpeed: managementState?.standardWindSpeedId ?? '',
|
||||
stdSnowLd: managementState?.verticalSnowCover ?? '',
|
||||
inclCd: selectedRoof?.pitch ?? 0,
|
||||
roofPitch: Math.round(hajebichi ?? 0),
|
||||
constTp: constructionList[index].constTp,
|
||||
snowGdPossYn: constructionList[index].snowGdPossYn,
|
||||
cvrYn: constructionList[index].cvrYn,
|
||||
mixMatlNo: selectedModules.mixMatlNo,
|
||||
// workingWidth: selectedRoof?.length?.toString() ?? '',
|
||||
workingWidth: lengthBase,
|
||||
},
|
||||
})
|
||||
|
||||
setCvrYn(constructionList[index].cvrYn)
|
||||
setSnowGdPossYn(constructionList[index].snowGdPossYn)
|
||||
setCvrChecked(false)
|
||||
setSnowGdChecked(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleChangeRoofMaterial = (index) => {
|
||||
const newAddedRoofs = roofs.map((roof, i) => {
|
||||
if (i === selectedRoof.index) {
|
||||
return {
|
||||
...selectedRoof,
|
||||
hajebichi,
|
||||
length: lengthBase,
|
||||
eavesMargin,
|
||||
ridgeMargin,
|
||||
kerabaMargin,
|
||||
roofIndex: selectedRoof.index,
|
||||
raft: selectedRaftBase?.clCode ?? selectedRoof?.roofBaseCd,
|
||||
trestle: {
|
||||
hajebichi: hajebichi,
|
||||
length: lengthBase,
|
||||
...selectedRaftBase,
|
||||
...selectedTrestle,
|
||||
...selectedConstMthd,
|
||||
...selectedRoofBase,
|
||||
},
|
||||
construction: {
|
||||
...constructionList.find((data) => data.constTp === trestleState.constTp),
|
||||
cvrYn: cvrYn,
|
||||
snowGdPossYn: snowGdPossYn,
|
||||
cvrChecked: cvrChecked,
|
||||
snowGdChecked: snowGdChecked,
|
||||
setupCover: cvrChecked ?? false,
|
||||
setupSnowCover: snowGdChecked ?? false,
|
||||
},
|
||||
trestleDetail: trestleDetail,
|
||||
}
|
||||
}
|
||||
return roof
|
||||
})
|
||||
setRoofs(newAddedRoofs)
|
||||
setSelectedRoof(newAddedRoofs[index])
|
||||
}
|
||||
|
||||
const isComplete = async () => {
|
||||
const newAddedRoofs = roofs.map((roof, i) => {
|
||||
if (i === selectedRoof?.index) {
|
||||
return {
|
||||
...selectedRoof,
|
||||
length: lengthBase,
|
||||
eavesMargin,
|
||||
ridgeMargin,
|
||||
kerabaMargin,
|
||||
roofIndex: roof.index,
|
||||
raft: selectedRaftBase?.clCode ?? selectedRoof?.raft ?? '',
|
||||
//hajebichi: selectedRaftBase?.hajebichi ?? selectedRoof?.hajebichi ?? 0,
|
||||
trestle: {
|
||||
length: lengthBase,
|
||||
hajebichi: hajebichi,
|
||||
...selectedRaftBase,
|
||||
...selectedTrestle,
|
||||
...selectedConstMthd,
|
||||
...selectedRoofBase,
|
||||
},
|
||||
construction: {
|
||||
//...constructionList.find((data) => newAddedRoofs[index].construction.constTp === data.constTp),
|
||||
...constructionList.find((data) => trestleState.constTp === data.constTp),
|
||||
cvrYn,
|
||||
snowGdPossYn,
|
||||
cvrChecked,
|
||||
snowGdChecked,
|
||||
setupCover: cvrChecked ?? false,
|
||||
setupSnowCover: snowGdChecked ?? false,
|
||||
},
|
||||
trestleDetail: trestleDetail,
|
||||
}
|
||||
}
|
||||
return roof
|
||||
})
|
||||
|
||||
let result = true
|
||||
for (let i = 0; i < newAddedRoofs.length; i++) {
|
||||
const roof = newAddedRoofs[i]
|
||||
|
||||
if (!roof.trestle?.trestleMkrCd) {
|
||||
Swal.fire({
|
||||
title: getMessage('modal.module.basic.settting.module.error1', [roof.nameJp]), // 가대메이커를 선택해주세요.
|
||||
icon: 'warning',
|
||||
})
|
||||
result = false
|
||||
return false
|
||||
}
|
||||
|
||||
if (!roof.trestle?.constMthdCd) {
|
||||
Swal.fire({
|
||||
title: getMessage('modal.module.basic.settting.module.error2', [roof.nameJp]), // 공법을 선택해주세요.
|
||||
icon: 'warning',
|
||||
})
|
||||
result = false
|
||||
return false
|
||||
}
|
||||
if (!roof.trestle?.roofBaseCd) {
|
||||
Swal.fire({
|
||||
title: getMessage('modal.module.basic.settting.module.error3', [roof.nameJp]), // 지붕밑바탕을 선택해주세요.
|
||||
icon: 'warning',
|
||||
})
|
||||
result = false
|
||||
return false
|
||||
}
|
||||
if (!roof.construction?.constTp) {
|
||||
Swal.fire({
|
||||
title: getMessage('modal.module.basic.settting.module.error12', [roof.nameJp]), // 시공법법을 선택해주세요.
|
||||
icon: 'warning',
|
||||
})
|
||||
result = false
|
||||
return false
|
||||
}
|
||||
|
||||
if (roof.lenAuth === 'C') {
|
||||
if (!roof.trestle?.length) {
|
||||
Swal.fire({
|
||||
title: getMessage('modal.module.basic.settting.module.error5', [roof.nameJp]), // L 값을 입력해주세요.
|
||||
icon: 'warning',
|
||||
})
|
||||
result = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (['C', 'R'].includes(roof.raftAuth)) {
|
||||
if (!roof?.raft) {
|
||||
Swal.fire({
|
||||
title: getMessage('modal.module.basic.settting.module.error6', [roof.nameJp]), // 서까래 간격을 입력해주세요.
|
||||
icon: 'warning',
|
||||
})
|
||||
result = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (['C', 'R'].includes(roof.roofPchAuth)) {
|
||||
if (!roof?.hajebichi) {
|
||||
Swal.fire({
|
||||
title: getMessage('modal.module.basic.settting.module.error7', [roof.nameJp]), // 하제비치를 입력해주세요.
|
||||
icon: 'warning',
|
||||
})
|
||||
result = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (!roof?.eavesMargin || !roof?.ridgeMargin || !roof?.kerabaMargin) {
|
||||
Swal.fire({
|
||||
title: getMessage('modal.module.basic.settting.module.error8', [roof.nameJp]), // 모듈 배치 영영 값을 입력해주세요.
|
||||
icon: 'warning',
|
||||
})
|
||||
result = false
|
||||
return false
|
||||
}
|
||||
|
||||
if (roof.trestle.trestleMkrCd !== 'NO_DATA') {
|
||||
// 가매 없음이 아닐때는 가대 정보보다 작을 수 없음
|
||||
if (roof.trestleDetail?.eaveIntvl > roof.eavesMargin) {
|
||||
Swal.fire({
|
||||
title: getMessage('modal.module.basic.settting.module.error9', [roof.trestleDetail?.eaveIntvl, roof.nameJp]), // 모듈 배치 영역은 {0}mm 이상이어야 합니다.
|
||||
icon: 'warning',
|
||||
})
|
||||
result = false
|
||||
return false
|
||||
}
|
||||
|
||||
if (roof.trestleDetail?.ridgeIntvl > roof.ridgeMargin) {
|
||||
Swal.fire({
|
||||
title: getMessage('modal.module.basic.settting.module.error10', [roof.trestleDetail?.ridgeIntvl, roof.nameJp]), // 모듈 배치 영역은 {0}mm 이상이어야 합니다.
|
||||
icon: 'warning',
|
||||
})
|
||||
result = false
|
||||
return false
|
||||
}
|
||||
|
||||
if (roof.trestleDetail?.kerabaIntvl > roof.kerabaMargin) {
|
||||
Swal.fire({
|
||||
title: getMessage('modal.module.basic.settting.module.error11', [roof.trestleDetail?.kerabaIntvl, roof.nameJp]), // 모듈 배치 영역은 {0}mm 이상이어야 합니다.
|
||||
icon: 'warning',
|
||||
})
|
||||
result = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result) {
|
||||
const newRoofs = newAddedRoofs.map((roof) => {
|
||||
const { addRoof, construction, trestle, trestleDetail, roofConstructions, ...rest } = roof
|
||||
return rest
|
||||
})
|
||||
|
||||
setModuleSelectionData({
|
||||
...moduleSelectionData,
|
||||
roofConstructions: newAddedRoofs.map((roof, index) => ({
|
||||
roofIndex: newRoofs[index].index,
|
||||
trestle: roof.trestle,
|
||||
addRoof: newRoofs[index],
|
||||
construction: roof.construction,
|
||||
trestleDetail: roof.trestleDetail,
|
||||
})),
|
||||
})
|
||||
setFlag(true)
|
||||
tempModuleSelectionData.current = {
|
||||
...moduleSelectionData,
|
||||
roofConstructions: newAddedRoofs.map((roof, index) => ({
|
||||
roofIndex: newRoofs[index].index,
|
||||
trestle: roof.trestle,
|
||||
addRoof: newRoofs[index],
|
||||
construction: roof.construction,
|
||||
trestleDetail: roof.trestleDetail,
|
||||
})),
|
||||
}
|
||||
const updatePromises = [
|
||||
// new Promise((resolve) => {
|
||||
// resolve()
|
||||
// }),
|
||||
new Promise((resolve) => {
|
||||
setRoofs(newRoofs)
|
||||
resolve()
|
||||
}),
|
||||
|
||||
new Promise((resolve) => {
|
||||
const roofConstructions = newAddedRoofs.map((roof, index) => ({
|
||||
roofIndex: newRoofs[index].index,
|
||||
addRoof: newRoofs[index],
|
||||
trestle: {
|
||||
...roof.trestle,
|
||||
raft: roof.raftBaseCd,
|
||||
},
|
||||
construction: {
|
||||
// ...constructionList.find((construction) => newAddedRoofs[index].construction.constTp === construction.constTp),
|
||||
...roof.construction,
|
||||
roofIndex: roof.index,
|
||||
selectedIndex: roof.index,
|
||||
},
|
||||
trestleDetail: roof.trestleDetail,
|
||||
}))
|
||||
trestleTrigger({
|
||||
roofConstructions,
|
||||
})
|
||||
setRoofsStore(roofConstructions)
|
||||
resolve()
|
||||
}),
|
||||
]
|
||||
|
||||
await Promise.all(updatePromises)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
isComplete,
|
||||
}))
|
||||
|
||||
return (
|
||||
<div className="roof-module-tab2-overflow">
|
||||
<div className="module-table-box mb10">
|
||||
<div className="module-box-tab">
|
||||
{roofs &&
|
||||
roofs.map((roof, index) => (
|
||||
<button
|
||||
key={index}
|
||||
className={`module-btn ${selectedRoof?.index === index ? 'act' : ''}`}
|
||||
onClick={() => (roof ? handleChangeRoofMaterial(index) : null)}
|
||||
>
|
||||
{roof !== undefined ? `${roof.nameJp} (${currentAngleType === 'slope' ? roof.pitch : roof.angle}${pitchText})` : '-'}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="module-table-inner">
|
||||
<div className="module-table-flex-wrap tab2">
|
||||
<div className="module-flex-item">
|
||||
<div className="eaves-keraba-table">
|
||||
{selectedRoof && selectedRoof.lenAuth === 'C' && (
|
||||
<>
|
||||
<div className="eaves-keraba-item">
|
||||
<div className="eaves-keraba-th">L</div>
|
||||
<div className="eaves-keraba-td">
|
||||
<div className="grid-select">
|
||||
<input
|
||||
type="text"
|
||||
className="input-origin block"
|
||||
value={lengthBase}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value
|
||||
if (v === '') {
|
||||
onChangeLength('')
|
||||
return
|
||||
}
|
||||
const n = Number(normalizeDigits(v))
|
||||
if (Number.isNaN(n)) {
|
||||
onChangeLength('')
|
||||
} else {
|
||||
onChangeLength(n)
|
||||
}
|
||||
}}
|
||||
disabled={selectedRoof.lenAuth === 'R'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{selectedRoof && ['C', 'R'].includes(selectedRoof.raftAuth) && (
|
||||
<>
|
||||
<div className="eaves-keraba-item">
|
||||
<div className="eaves-keraba-th">{getMessage('modal.module.basic.setting.module.rafter.margin')}</div>
|
||||
<div className="eaves-keraba-td">
|
||||
<div className="grid-select">
|
||||
{raftBaseList.length > 0 && (
|
||||
<QSelectBox
|
||||
options={raftBaseList}
|
||||
value={selectedRaftBase}
|
||||
sourceKey={'clCode'}
|
||||
targetKey={'clCode'}
|
||||
showKey={'clCodeNm'}
|
||||
disabled={selectedRoof.raftAuth === 'R'}
|
||||
onChange={(e) => onChangeRaftBase(e)}
|
||||
showFirstOptionWhenEmpty={true}
|
||||
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{selectedRoof && ['C', 'R'].includes(selectedRoof.roofPchAuth) && (
|
||||
<>
|
||||
<div className="eaves-keraba-item">
|
||||
<div className="eaves-keraba-th">{getMessage('modal.module.basic.setting.module.hajebichi')}</div>
|
||||
<div className="eaves-keraba-td">
|
||||
<div className="grid-select">
|
||||
<input
|
||||
type="text"
|
||||
className="input-origin block"
|
||||
disabled={selectedRoof.roofPchAuth === 'R'}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value
|
||||
if (v === '') {
|
||||
onChangeHajebichi('')
|
||||
return
|
||||
}
|
||||
const n = Number(normalizeDigits(v))
|
||||
if (Number.isNaN(n)) {
|
||||
onChangeHajebichi('')
|
||||
} else {
|
||||
onChangeHajebichi(n)
|
||||
}
|
||||
}}
|
||||
value={hajebichi}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="eaves-keraba-item">
|
||||
<div className="eaves-keraba-th">{getMessage('modal.module.basic.setting.module.trestle.maker')}</div>
|
||||
<div className="eaves-keraba-td">
|
||||
<div className="grid-select">
|
||||
{trestleList && (
|
||||
<QSelectBox
|
||||
title={getMessage('selectbox.title')}
|
||||
options={trestleList}
|
||||
value={selectedTrestle}
|
||||
sourceKey={'trestleMkrCd'}
|
||||
targetKey={'trestleMkrCd'}
|
||||
showKey={'trestleMkrCdJp'}
|
||||
onChange={(e) => onChangeTrestleMaker(e)}
|
||||
showFirstOptionWhenEmpty={true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="eaves-keraba-item">
|
||||
<div className="eaves-keraba-th">{getMessage('modal.module.basic.setting.module.construction.method')}</div>
|
||||
<div className="eaves-keraba-td">
|
||||
<div className="grid-select">
|
||||
{constMthdList && (
|
||||
<QSelectBox
|
||||
title={getMessage('selectbox.title')}
|
||||
options={constMthdList}
|
||||
value={selectedConstMthd}
|
||||
sourceKey={'constMthdCd'}
|
||||
targetKey={'constMthdCd'}
|
||||
showKey={'constMthdCdJp'}
|
||||
onChange={(e) => onChangeConstMthd(e)}
|
||||
showFirstOptionWhenEmpty={true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="eaves-keraba-item">
|
||||
<div className="eaves-keraba-th">{getMessage('modal.module.basic.setting.module.under.roof')}</div>
|
||||
<div className="eaves-keraba-td">
|
||||
<div className="grid-select">
|
||||
{roofBaseList && (
|
||||
<QSelectBox
|
||||
title={getMessage('selectbox.title')}
|
||||
options={roofBaseList}
|
||||
sourceKey={'roofBaseCd'}
|
||||
targetKey={'roofBaseCd'}
|
||||
showKey={'roofBaseCdJp'}
|
||||
value={selectedRoofBase}
|
||||
onChange={(e) => onChangeRoofBase(e)}
|
||||
showFirstOptionWhenEmpty={true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="module-flex-item non-flex">
|
||||
<div className="flex-item-btn-wrap">
|
||||
<button className={`btn-frame roof ${getConstructionState(0)}`} onClick={() => handleConstruction(0)}>
|
||||
{getMessage('modal.module.basic.setting.module.standard.construction')}(I)
|
||||
</button>
|
||||
<button className={`btn-frame roof ${getConstructionState(3)}`} onClick={() => handleConstruction(3)}>
|
||||
{getMessage('modal.module.basic.setting.module.multiple.construction')}
|
||||
</button>
|
||||
<button className={`btn-frame roof ${getConstructionState(1)}`} onClick={() => handleConstruction(1)}>
|
||||
{getMessage('modal.module.basic.setting.module.standard.construction')}
|
||||
</button>
|
||||
<button className={`btn-frame roof ${getConstructionState(4)}`} onClick={() => handleConstruction(4)}>
|
||||
{getMessage('modal.module.basic.setting.module.multiple.construction')}(II)
|
||||
</button>
|
||||
<button className={`btn-frame roof ${getConstructionState(2)}`} onClick={() => handleConstruction(2)}>
|
||||
{getMessage('modal.module.basic.setting.module.enforce.construction')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid-check-form-flex">
|
||||
<div className="d-check-box pop">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`ch01`}
|
||||
disabled={!cvrYn || cvrYn === 'N'}
|
||||
checked={cvrChecked || false}
|
||||
// onChange={() => dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, cvrChecked: !trestleState.cvrChecked } })}
|
||||
onChange={() => setCvrChecked(!cvrChecked)}
|
||||
/>
|
||||
<label htmlFor={`ch01`}>{getMessage('modal.module.basic.setting.module.eaves.bar.fitting')}</label>
|
||||
</div>
|
||||
<div className="d-check-box pop">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`ch02`}
|
||||
disabled={!snowGdPossYn || snowGdPossYn === 'N'}
|
||||
checked={snowGdChecked || false}
|
||||
// onChange={() => dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, snowGdChecked: !trestleState.snowGdChecked } })}
|
||||
onChange={() => setSnowGdChecked(!snowGdChecked)}
|
||||
/>
|
||||
<label htmlFor={`ch02`}>{getMessage('modal.module.basic.setting.module.blind.metal.fitting')}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="module-input-area">
|
||||
<div className="module-area-title">{getMessage('modal.module.basic.setting.module.placement.area')}</div>
|
||||
<div className="module-input-wrap">
|
||||
<div className="outline-form mr15">
|
||||
<span>{getMessage('modal.module.basic.setting.module.placement.area.eaves')}</span>
|
||||
<div className="input-grid mr10">
|
||||
<input
|
||||
type="number"
|
||||
className="input-origin block"
|
||||
value={eavesMargin ?? 0}
|
||||
// onChange={(e) => dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, eavesMargin: e.target.value } })}
|
||||
onChange={(e) => setEavesMargin(+e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<span className="thin">mm</span>
|
||||
</div>
|
||||
<div className="outline-form mr15">
|
||||
<span>{getMessage('modal.module.basic.setting.module.placement.area.ridge')}</span>
|
||||
<div className="input-grid mr10">
|
||||
<input
|
||||
type="number"
|
||||
className="input-origin block"
|
||||
value={ridgeMargin ?? 0}
|
||||
// onChange={(e) => dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, ridgeMargin: e.target.value } })}
|
||||
onChange={(e) => setRidgeMargin(+e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<span className="thin">mm</span>
|
||||
</div>
|
||||
<div className="outline-form ">
|
||||
<span>{getMessage('modal.module.basic.setting.module.placement.area.keraba')}</span>
|
||||
<div className="input-grid mr10">
|
||||
<input
|
||||
type="number"
|
||||
className="input-origin block"
|
||||
value={kerabaMargin ?? 0}
|
||||
// onChange={(e) => dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, kerabaMargin: e.target.value } })}
|
||||
onChange={(e) => setKerabaMargin(+e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<span className="thin">mm</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="module-input-area">
|
||||
<div className="module-area-title">{getMessage('modal.module.basic.setting.module.placement.margin')}</div>
|
||||
<div className="module-input-wrap">
|
||||
<div className="outline-form">
|
||||
<span>{getMessage('modal.module.basic.setting.module.placement.margin.horizontal')}</span>
|
||||
<div className="input-grid mr10">
|
||||
<input type="text" className="input-origin block" defaultValue={trestleDetail?.moduleIntvlHor} readOnly />
|
||||
</div>
|
||||
<span className="thin">mm</span>
|
||||
</div>
|
||||
<div className="outline-form">
|
||||
<span>{getMessage('modal.module.basic.setting.module.placement.margin.vertical')}</span>
|
||||
<div className="input-grid mr10">
|
||||
<input type="text" className="input-origin block" defaultValue={trestleDetail?.moduleIntvlVer} readOnly />
|
||||
</div>
|
||||
<span className="thin">mm</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="module-bottom">
|
||||
<div className="module-table-box ">
|
||||
<div className="warning-guide">
|
||||
<div className="warning">
|
||||
{getMessage('modal.module.basic.setting.module.setting.info1')}
|
||||
<br />
|
||||
{getMessage('modal.module.basic.setting.module.setting.info2')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
export default Trestle
|
||||
@ -33,14 +33,6 @@ const PitchPlacement = forwardRef((props, refs) => {
|
||||
setSelectedItems({ ...selectedItems, [e.target.name]: e.target.checked })
|
||||
}
|
||||
|
||||
const moduleData = {
|
||||
header: [
|
||||
{ type: 'check', name: '', prop: 'check', width: 70 },
|
||||
{ type: 'color-box', name: getMessage('module'), prop: 'module' },
|
||||
{ type: 'text', name: `${getMessage('output')} (W)`, prop: 'output', width: 70 },
|
||||
],
|
||||
}
|
||||
|
||||
//체크된 모듈 데이터
|
||||
useEffect(() => {
|
||||
const checkedModuleIds = Object.keys(selectedItems).filter((key) => selectedItems[key])
|
||||
@ -105,87 +97,91 @@ const PitchPlacement = forwardRef((props, refs) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="module-table-box mb10">
|
||||
<div className="module-table-inner">
|
||||
<div className="roof-module-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{moduleData.header.map((data) => (
|
||||
<th key={data.prop} style={{ width: data.width ? data.width : '' }}>
|
||||
{data.type === 'check' ? (
|
||||
<div className="d-check-box no-text pop">
|
||||
<input type="checkbox" id="ch01" disabled />
|
||||
<label htmlFor="ch01"></label>
|
||||
</div>
|
||||
) : (
|
||||
data.name
|
||||
)}
|
||||
<div className="hexagonal-flex-wrap">
|
||||
<div className="module-table-box ">
|
||||
<div className="module-table-inner">
|
||||
<div className="roof-module-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ width: '70px' }}>
|
||||
<div className="d-check-box no-text pop">
|
||||
<input type="checkbox" id="ch01" disabled />
|
||||
<label htmlFor="ch01"></label>
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{selectedModules.itemList &&
|
||||
selectedModules.itemList.map((item, index) => (
|
||||
<tr key={index}>
|
||||
<td className="al-c">
|
||||
<div className="d-check-box no-text pop">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={item.itemId}
|
||||
name={item.itemId}
|
||||
checked={selectedItems[item.itemId]}
|
||||
onChange={handleSelectedItem}
|
||||
/>
|
||||
<label htmlFor={item.itemId}></label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="color-wrap">
|
||||
<span className="color-box" style={{ backgroundColor: item.color }}></span>
|
||||
<span className="name">{item.itemNm}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="al-r">{item.wpOut}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<th>{getMessage('module')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{selectedModules.itemList &&
|
||||
selectedModules.itemList.map((item, index) => (
|
||||
<tr key={index}>
|
||||
<td className="al-c">
|
||||
<div className="d-check-box no-text pop">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={item.itemId}
|
||||
name={item.itemId}
|
||||
checked={selectedItems[item.itemId]}
|
||||
onChange={handleSelectedItem}
|
||||
/>
|
||||
<label htmlFor={item.itemId}></label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="color-wrap">
|
||||
<span className="color-box" style={{ backgroundColor: item.color }}></span>
|
||||
<span className="name">{item.itemNm}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="module-table-box mb10">
|
||||
<div className="module-table-inner">
|
||||
<div className="hexagonal-wrap">
|
||||
<div className="hexagonal-item">
|
||||
<div className="bold-font">{getMessage('modal.module.basic.setting.pitch.module.placement.standard.setting')}</div>
|
||||
</div>
|
||||
<div className="hexagonal-item">
|
||||
<div className="pop-form-radio">
|
||||
<div className="d-check-radio pop">
|
||||
<input
|
||||
type="radio"
|
||||
name="radio01"
|
||||
id="ra01"
|
||||
value={'south'}
|
||||
defaultChecked={setupLocation === 'south'}
|
||||
onClick={handleSetupLocation}
|
||||
/>
|
||||
<label htmlFor="ra01">{getMessage('modal.module.basic.setting.pitch.module.placement.standard.setting.south')}</label>
|
||||
</div>
|
||||
<div className="d-check-radio pop">
|
||||
<input
|
||||
type="radio"
|
||||
name="radio01"
|
||||
id="ra02"
|
||||
value={'excreta'}
|
||||
defaultChecked={setupLocation === 'excreta'}
|
||||
onClick={handleSetupLocation}
|
||||
/>
|
||||
<label htmlFor="ra02">{getMessage('modal.module.basic.setting.pitch.module.placement.standard.setting.select')}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="module-table-box non-flex">
|
||||
<div className="module-table-inner">
|
||||
<div className="roof-module-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{getMessage('modal.module.basic.setting.pitch.module.placement.standard.setting')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div className="hexagonal-radio-wrap">
|
||||
<div className="d-check-radio pop mb10">
|
||||
<input
|
||||
type="radio"
|
||||
name="radio01"
|
||||
id="ra01"
|
||||
value={'south'}
|
||||
defaultChecked={setupLocation === 'south'}
|
||||
onClick={handleSetupLocation}
|
||||
/>
|
||||
<label htmlFor="ra01">{getMessage('modal.module.basic.setting.pitch.module.placement.standard.setting.south')}</label>
|
||||
</div>
|
||||
<div className="d-check-radio pop">
|
||||
<input
|
||||
type="radio"
|
||||
name="radio01"
|
||||
id="ra02"
|
||||
value={'excreta'}
|
||||
defaultChecked={setupLocation === 'excreta'}
|
||||
onClick={handleSetupLocation}
|
||||
/>
|
||||
<label htmlFor="ra02">{getMessage('modal.module.basic.setting.pitch.module.placement.standard.setting.select')}</label>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,30 +1,27 @@
|
||||
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 } from '@/store/canvasAtom'
|
||||
import { canvasState, canvasZoomState } from '@/store/canvasAtom'
|
||||
|
||||
import { useTrestle } from '@/hooks/module/useTrestle'
|
||||
import { selectedModuleState } from '@/store/selectedModuleOptions'
|
||||
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { useEstimate } from '@/hooks/useEstimate'
|
||||
import { useCircuitTrestle } from '@/hooks/useCirCuitTrestle'
|
||||
import { useCanvasPopupStatusController } from '@/hooks/common/useCanvasPopupStatusController'
|
||||
import { useImgLoader } from '@/hooks/floorPlan/useImgLoader'
|
||||
import { usePlan } from '@/hooks/usePlan'
|
||||
import { QcastContext } from '@/app/QcastProvider'
|
||||
import { fabric } from 'fabric'
|
||||
import { fontSelector } from '@/store/fontAtom'
|
||||
|
||||
const ALLOCATION_TYPE = {
|
||||
AUTO: 'auto',
|
||||
@ -37,13 +34,16 @@ export default function CircuitTrestleSetting({ id }) {
|
||||
const { swalFire } = useSwal()
|
||||
const { saveEstimate } = useEstimate()
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
|
||||
const [canvasZoom, setCanvasZoom] = useRecoilState(canvasZoomState)
|
||||
const [tabNum, setTabNum] = useState(1)
|
||||
const [allocationType, setAllocationType] = useState(ALLOCATION_TYPE.AUTO)
|
||||
const [circuitAllocationType, setCircuitAllocationType] = useState(1)
|
||||
const { managementState, setManagementState, managementStateLoaded } = useContext(GlobalDataContext)
|
||||
const { managementState, setManagementState } = useContext(GlobalDataContext)
|
||||
const selectedModules = useRecoilValue(selectedModuleState)
|
||||
const { getPcsAutoRecommendList, getPcsVoltageChk, getPcsVoltageStepUpList, getPcsManualConfChk } = useMasterController()
|
||||
const flowText = useRecoilValue(fontSelector('flowText'))
|
||||
const lengthText = useRecoilValue(fontSelector('lengthText'))
|
||||
const circuitNumberText = useRecoilValue(fontSelector('circuitNumberText'))
|
||||
|
||||
// 회로할당(승합설정)에서 선택된 값들을 저장할 상태 추가
|
||||
const [selectedStepUpValues, setSelectedStepUpValues] = useState({})
|
||||
@ -55,7 +55,7 @@ export default function CircuitTrestleSetting({ id }) {
|
||||
const [seletedSubOption, setSeletedSubOption] = useState(null)
|
||||
const { setModuleStatisticsData } = useCircuitTrestle()
|
||||
const { handleCanvasToPng } = useImgLoader()
|
||||
|
||||
const moduleSelectionData = useRecoilValue(moduleSelectionDataState)
|
||||
const passivityCircuitAllocationRef = useRef()
|
||||
const { setIsGlobalLoading } = useContext(QcastContext)
|
||||
|
||||
@ -84,7 +84,6 @@ export default function CircuitTrestleSetting({ id }) {
|
||||
// const { trigger: moduleSelectedDataTrigger } = useCanvasPopupStatusController(2)
|
||||
useEffect(() => {
|
||||
if (!managementState) {
|
||||
setManagementState(managementStateLoaded)
|
||||
}
|
||||
// setCircuitData({
|
||||
// makers,
|
||||
@ -103,6 +102,115 @@ 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 = (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')
|
||||
canvas.renderAll()
|
||||
}
|
||||
|
||||
// 캡쳐 후 처리
|
||||
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()
|
||||
}
|
||||
|
||||
// 수동할당 시 모듈 삭제
|
||||
|
||||
// 시리즈중 자동으로 추천 PCS 정보 조회
|
||||
@ -155,6 +263,7 @@ export default function CircuitTrestleSetting({ id }) {
|
||||
getPcsVoltageChk(pcsVoltageChkParams).then((res) => {
|
||||
if (res.resultCode === 'S') {
|
||||
setTabNum(2)
|
||||
setAllModuleSurfaceIsComplete(false)
|
||||
} else {
|
||||
swalFire({
|
||||
title: res.resultMsg,
|
||||
@ -188,6 +297,7 @@ export default function CircuitTrestleSetting({ id }) {
|
||||
}).then((res) => {
|
||||
if (res?.result.resultCode === 'S' && res?.data) {
|
||||
setTabNum(2)
|
||||
setAllModuleSurfaceIsComplete(false)
|
||||
} else {
|
||||
swalFire({ text: getMessage('common.message.send.error') })
|
||||
}
|
||||
@ -287,6 +397,8 @@ export default function CircuitTrestleSetting({ id }) {
|
||||
setSelectedModels(pcsItemList)
|
||||
getPcsVoltageChk(pcsVoltageChkParams).then((res) => {
|
||||
setAllocationType(ALLOCATION_TYPE.PASSIVITY)
|
||||
setAllModuleSurfaceIsComplete(false)
|
||||
clearTrestle()
|
||||
})
|
||||
} else {
|
||||
swalFire({
|
||||
@ -309,8 +421,15 @@ export default function CircuitTrestleSetting({ id }) {
|
||||
|
||||
const target = pcsCheck.max ? moduleMaxQty : moduleStdQty
|
||||
const placementModules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE)
|
||||
let moduleAmount = placementModules.reduce((acc, module) => {
|
||||
if (moduleSelectionData.module.itemList.length === 1 || module.moduleInfo.itemId === moduleSelectionData.module.itemList[0].itemId) {
|
||||
return acc + 1
|
||||
} else {
|
||||
return acc + 0.66
|
||||
}
|
||||
}, 0)
|
||||
|
||||
if (placementModules.length > target) {
|
||||
if (moduleAmount > target) {
|
||||
swalFire({
|
||||
title: getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.all.power.conditional.validation.error01'),
|
||||
type: 'alert',
|
||||
@ -319,6 +438,7 @@ export default function CircuitTrestleSetting({ id }) {
|
||||
}
|
||||
|
||||
setAllocationType(ALLOCATION_TYPE.PASSIVITY)
|
||||
clearTrestle()
|
||||
}
|
||||
}
|
||||
|
||||
@ -348,13 +468,9 @@ export default function CircuitTrestleSetting({ id }) {
|
||||
.map((obj) => {
|
||||
obj.pcses = getStepUpListData()
|
||||
})
|
||||
|
||||
setViewCircuitNumberTexts(false)
|
||||
|
||||
handleCanvasToPng(1)
|
||||
await capture(1)
|
||||
|
||||
//회로할당 저장 시 result=null인 경우에도 회로번호 텍스트 표시 유지 처리
|
||||
setViewCircuitNumberTexts(true)
|
||||
|
||||
// 회로할당 저장 클릭 시
|
||||
// 가대 및 지지금구 설치
|
||||
@ -369,7 +485,7 @@ export default function CircuitTrestleSetting({ id }) {
|
||||
const result = await getEstimateData()
|
||||
|
||||
if (result) {
|
||||
handleCanvasToPng(2)
|
||||
await capture(2)
|
||||
// 견적서 저장
|
||||
await saveEstimate(result)
|
||||
} else {
|
||||
@ -379,6 +495,16 @@ export default function CircuitTrestleSetting({ id }) {
|
||||
// removeNotAllocationModules()
|
||||
}
|
||||
|
||||
const changeFontSize = (name, size) => {
|
||||
const textObjs = canvas?.getObjects().filter((obj) => obj.name === name)
|
||||
textObjs.forEach((obj) => {
|
||||
obj.set({
|
||||
fontSize: size,
|
||||
})
|
||||
})
|
||||
canvas.renderAll()
|
||||
}
|
||||
|
||||
// 이전 버튼 클릭 시
|
||||
const onClickPrev = () => {
|
||||
// setAllocationType(ALLOCATION_TYPE.AUTO)
|
||||
@ -479,7 +605,7 @@ export default function CircuitTrestleSetting({ id }) {
|
||||
console.log(stepUpListData)
|
||||
stepUpListData[0].pcsItemList.map((item, index) => {
|
||||
return item.serQtyList
|
||||
.filter((serQty) => serQty.selected)
|
||||
.filter((serQty) => serQty.selected && serQty.paralQty > 0)
|
||||
.forEach((serQty) => {
|
||||
pcs.push({
|
||||
pcsMkrCd: item.pcsMkrCd,
|
||||
@ -655,6 +781,7 @@ export default function CircuitTrestleSetting({ id }) {
|
||||
return
|
||||
} else {
|
||||
setTabNum(2)
|
||||
setAllModuleSurfaceIsComplete(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import { useMasterController } from '@/hooks/common/useMasterController'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { useSwal } from '@/hooks/useSwal'
|
||||
import { pcsCheckState } from '@/store/circuitTrestleAtom'
|
||||
import { sessionStore } from '@/store/commonAtom'
|
||||
import { globalLocaleStore } from '@/store/localeAtom'
|
||||
import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions'
|
||||
import { isNullOrUndefined } from '@/util/common-utils'
|
||||
@ -38,13 +39,14 @@ export default function PowerConditionalSelect(props) {
|
||||
} = props
|
||||
const [pcsCheck, setPcsCheck] = useRecoilState(pcsCheckState)
|
||||
|
||||
const sessionState = useRecoilValue(sessionStore)
|
||||
const { getMessage } = useMessage()
|
||||
const [selectedRow, setSelectedRow] = useState(null)
|
||||
const globalLocale = useRecoilValue(globalLocaleStore)
|
||||
const { getPcsMakerList, getPcsModelList } = useMasterController()
|
||||
const selectedModules = useRecoilValue(selectedModuleState)
|
||||
const { swalFire } = useSwal()
|
||||
const { trigger: moduleSelectedDataTrigger } = useCanvasPopupStatusController(2)
|
||||
// const { trigger: moduleSelectedDataTrigger } = useCanvasPopupStatusController(2)
|
||||
const [moduleSelectionData, setModuleSelectionData] = useRecoilState(moduleSelectionDataState)
|
||||
const modelHeader = [
|
||||
{ name: getMessage('modal.circuit.trestle.setting.circuit.allocation.passivity.series'), width: '15%', prop: 'pcsSerNm', type: 'color-box' },
|
||||
@ -72,11 +74,9 @@ export default function PowerConditionalSelect(props) {
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
if (makers.length === 0) {
|
||||
getPcsMakerList().then((res) => {
|
||||
setMakers(res.data)
|
||||
})
|
||||
}
|
||||
getPcsMakerList().then((res) => {
|
||||
setMakers(res.data)
|
||||
})
|
||||
}, [])
|
||||
|
||||
const onCheckSeries = (data) => {
|
||||
@ -110,6 +110,7 @@ export default function PowerConditionalSelect(props) {
|
||||
selected: s.pcsSerCd === data.pcsSerCd ? !s.selected : false,
|
||||
}
|
||||
})
|
||||
setSelectedModels([])
|
||||
}
|
||||
setSeries(copySeries)
|
||||
handleSetmodels(copySeries.filter((s) => s.selected))
|
||||
@ -131,7 +132,7 @@ export default function PowerConditionalSelect(props) {
|
||||
mixMatlNo: item.mixMatlNo,
|
||||
}
|
||||
})
|
||||
getPcsModelList({ pcsMkrCd, pcsSerList, moduleItemList }).then((res) => {
|
||||
getPcsModelList({ pcsMkrCd, pcsSerList, moduleItemList, storeId: sessionState.storeId }).then((res) => {
|
||||
if (res?.result.code === 200 && res?.data) {
|
||||
setModels(
|
||||
res.data.map((model) => {
|
||||
@ -176,7 +177,7 @@ export default function PowerConditionalSelect(props) {
|
||||
|
||||
if (selectedMaker.pcsMkrMultiType === PCS_MKR_MULTI_TYPE.MULTI) {
|
||||
setSelectedModels([...selectedModels, { ...selectedRow, id: uuidv4() }])
|
||||
} else if (!selectedModels.find((m) => m.itemId === selectedRow.itemId)) {
|
||||
} else if (!selectedModels.find((m) => m.itemId === selectedRow.itemId && m.pcsSerCd === selectedRow.pcsSerCd)) {
|
||||
setSelectedModels([...selectedModels, { ...selectedRow, id: uuidv4() }])
|
||||
}
|
||||
setSelectedRow(null)
|
||||
|
||||
@ -42,7 +42,6 @@ export default function StepUp(props) {
|
||||
const [arrayLength, setArrayLength] = useState(3) //module-table-inner의 반복 개수
|
||||
const [pcsCheck, setPcsCheck] = useRecoilState(pcsCheckState)
|
||||
const { getPcsVoltageStepUpList, getPcsAutoRecommendList, getPcsVoltageChk, getPcsConnOptionItemList } = useMasterController()
|
||||
const { managementState, setManagementState, managementStateLoaded } = useContext(GlobalDataContext)
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
const selectedModules = useRecoilValue(selectedModuleState)
|
||||
const [optCodes, setOptCodes] = useState([])
|
||||
@ -110,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()) {
|
||||
@ -165,6 +165,8 @@ export default function StepUp(props) {
|
||||
targetModule.pcsItemId = module.pcsItemId
|
||||
targetModule.circuitNumber = module.circuit
|
||||
canvas.add(moduleCircuitText)
|
||||
} else {
|
||||
mChk++;
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -173,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') })
|
||||
}
|
||||
@ -468,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) => {
|
||||
@ -517,7 +523,7 @@ export default function StepUp(props) {
|
||||
|
||||
canvas.renderAll()
|
||||
setModuleStatisticsData()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 선택된 값들을 가져오는 함수 추가
|
||||
@ -573,7 +579,7 @@ export default function StepUp(props) {
|
||||
value={seletedMainOption}
|
||||
sourceKey="code"
|
||||
targetKey="code"
|
||||
showKey="name"
|
||||
showKey={`${globalLocale === 'ja' ? 'nameJp' : 'name'}`}
|
||||
onChange={(e) => setSeletedMainOption(e)}
|
||||
/>
|
||||
)}
|
||||
@ -586,7 +592,7 @@ export default function StepUp(props) {
|
||||
value={seletedSubOption}
|
||||
sourceKey="code"
|
||||
targetKey="code"
|
||||
showKey="name"
|
||||
showKey={`${globalLocale === 'ja' ? 'nameJp' : 'name'}`}
|
||||
onChange={(e) => setSeletedSubOption(e)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -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 {
|
||||
@ -25,7 +26,7 @@ export default function PassivityCircuitAllocation(props) {
|
||||
const { swalFire } = useSwal()
|
||||
const { getMessage } = useMessage()
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
const { managementState, setManagementState, managementStateLoaded } = useContext(GlobalDataContext)
|
||||
const { managementState } = useContext(GlobalDataContext)
|
||||
const selectedModules = useRecoilValue(selectedModuleState)
|
||||
const [selectedPcs, setSelectedPcs] = useState(selectedModels[0])
|
||||
const { header, rows, footer } = useRecoilValue(moduleStatisticsState)
|
||||
@ -38,7 +39,6 @@ export default function PassivityCircuitAllocation(props) {
|
||||
useEffect(() => {
|
||||
setModuleStatisticsData()
|
||||
if (!managementState) {
|
||||
setManagementState(managementStateLoaded)
|
||||
}
|
||||
canvas
|
||||
.getObjects()
|
||||
@ -87,6 +87,26 @@ export default function PassivityCircuitAllocation(props) {
|
||||
.map((obj) => obj.circuitNumber),
|
||||
),
|
||||
]
|
||||
|
||||
const surfaceList = targetModules.map((module) => {
|
||||
return canvas.getObjects().filter((obj) => obj.id === canvas.getObjects().filter((obj) => obj.id === module)[0].surfaceId)[0]
|
||||
})
|
||||
|
||||
if (surfaceList.length > 1) {
|
||||
let surfaceType = {}
|
||||
|
||||
surfaceList.forEach((surface) => {
|
||||
surfaceType[`${surface.direction}-${surface.roofMaterial.pitch}`] = surface
|
||||
})
|
||||
if (Object.keys(surfaceType).length > 1) {
|
||||
swalFire({
|
||||
text: getMessage('module.circuit.fix.not.same.roof.error'),
|
||||
type: 'alert',
|
||||
icon: 'warning',
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!circuitNumber || circuitNumber === 0) {
|
||||
swalFire({
|
||||
text: getMessage('module.circuit.minimun.error'),
|
||||
@ -561,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()}>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -18,12 +18,14 @@ export default function WallMerge({ offsetRef, radioTypeRef }) {
|
||||
<div className="eaves-keraba-th">
|
||||
<div className="d-check-radio pop">
|
||||
<input type="radio" name="radio01" id="ra01" value="1" checked={type === '1'} onChange={(e) => onChange(e)} />
|
||||
<label htmlFor="ra01">{getMessage('has.sleeve')}</label>
|
||||
<label htmlFor="ra01">{getMessage('has.not.sleeve')}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="eaves-keraba-td">
|
||||
<div className={`eaves-keraba-ico ${type === '1' ? 'act' : ''}`}>
|
||||
<Image src="/static/images/canvas/eaves_icon06.svg" alt="react" width={30} height={30} />
|
||||
<div style={{ width: 30, height: 30, position: 'relative' }}>
|
||||
<Image src="/static/images/canvas/eaves_icon06.svg" alt="react" fill style={{ objectFit: 'contain' }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -31,12 +33,14 @@ export default function WallMerge({ offsetRef, radioTypeRef }) {
|
||||
<div className="eaves-keraba-th">
|
||||
<div className="d-check-radio pop">
|
||||
<input type="radio" name="radio01" id="ra02" value="2" checked={type === '2'} onChange={(e) => onChange(e)} />
|
||||
<label htmlFor="ra02">{getMessage('has.not.sleeve')}</label>
|
||||
<label htmlFor="ra02">{getMessage('has.sleeve')}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="eaves-keraba-td">
|
||||
<div className={`eaves-keraba-ico ${type === '2' ? 'act' : ''}`}>
|
||||
<Image src="/static/images/canvas/eaves_icon07.svg" alt="react" width={30} height={30} />
|
||||
<div style={{ width: 30, height: 30, position: 'relative' }}>
|
||||
<Image src="/static/images/canvas/eaves_icon07.svg" alt="react" fill style={{ objectFit: 'contain' }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -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>
|
||||
|
||||
@ -7,6 +7,8 @@ import { useMessage } from '@/hooks/useMessage'
|
||||
import { usePopup } from '@/hooks/usePopup'
|
||||
import { canvasState } from '@/store/canvasAtom'
|
||||
import { usePolygon } from '@/hooks/usePolygon'
|
||||
import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch'
|
||||
import { useRoofFn } from '@/hooks/common/useRoofFn'
|
||||
|
||||
const FLOW_DIRECTION_TYPE = {
|
||||
EIGHT_AZIMUTH: 'eightAzimuth',
|
||||
@ -18,6 +20,9 @@ export default function FlowDirectionSetting(props) {
|
||||
const { id, pos = contextPopupPosition, target } = props
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
const { getMessage } = useMessage()
|
||||
const { setSurfaceShapePattern } = useRoofFn()
|
||||
|
||||
const { changeSurfaceLineType } = useSurfaceShapeBatch({})
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@ -29,15 +34,40 @@ export default function FlowDirectionSetting(props) {
|
||||
const [flowDirection, setFlowDirection] = useState(target.direction)
|
||||
const { closePopup } = usePopup()
|
||||
|
||||
useEffect(() => {
|
||||
let newCompassDeg = 0
|
||||
if ([-15, 0, 15].includes(compasDeg)) {
|
||||
newCompassDeg = 0
|
||||
} else if ([30, 45, 60].includes(compasDeg)) {
|
||||
newCompassDeg = 45
|
||||
} else if ([75, 90, 105].includes(compasDeg)) {
|
||||
newCompassDeg = 90
|
||||
} else if ([120, 135, 150].includes(compasDeg)) {
|
||||
newCompassDeg = 135
|
||||
} else if ([165, 180, -165].includes(compasDeg)) {
|
||||
newCompassDeg = 180
|
||||
} else if ([-120, -135, -150].includes(compasDeg)) {
|
||||
newCompassDeg = -135
|
||||
} else if ([-105, -90, -75].includes(compasDeg)) {
|
||||
newCompassDeg = -90
|
||||
} else if ([-60, -45, -30].includes(compasDeg)) {
|
||||
newCompassDeg = -45
|
||||
} else {
|
||||
newCompassDeg = ''
|
||||
}
|
||||
const newOrientation = orientations.find((item) => item.value === newCompassDeg)
|
||||
setSelectedOrientation(newOrientation)
|
||||
}, [compasDeg])
|
||||
|
||||
const orientations = [
|
||||
{ name: `${getMessage('commons.none')}`, value: 0 },
|
||||
{ name: `${getMessage('commons.south')}`, value: 360 },
|
||||
{ name: `${getMessage('commons.south')}${getMessage('commons.east')}`, value: 315 },
|
||||
{ name: `${getMessage('commons.south')}${getMessage('commons.west')}`, value: 45 },
|
||||
{ name: `${getMessage('commons.east')}`, value: 270 },
|
||||
{ name: `${getMessage('commons.west')}`, value: 90 },
|
||||
{ name: `${getMessage('commons.north')}${getMessage('commons.east')}`, value: 225 },
|
||||
{ name: `${getMessage('commons.north')}${getMessage('commons.west')}`, value: 135 },
|
||||
{ name: `${getMessage('commons.none')}`, value: '' },
|
||||
{ name: `${getMessage('commons.south')}`, value: 0 },
|
||||
{ name: `${getMessage('commons.south')}${getMessage('commons.east')}`, value: 45 },
|
||||
{ name: `${getMessage('commons.south')}${getMessage('commons.west')}`, value: -45 },
|
||||
{ name: `${getMessage('commons.east')}`, value: 90 },
|
||||
{ name: `${getMessage('commons.west')}`, value: -90 },
|
||||
{ name: `${getMessage('commons.north')}${getMessage('commons.east')}`, value: 135 },
|
||||
{ name: `${getMessage('commons.north')}${getMessage('commons.west')}`, value: -135 },
|
||||
{ name: `${getMessage('commons.north')}`, value: 180 },
|
||||
]
|
||||
|
||||
@ -51,8 +81,10 @@ export default function FlowDirectionSetting(props) {
|
||||
surfaceCompass: orientation,
|
||||
surfaceCompassType: type,
|
||||
})
|
||||
setSurfaceShapePattern(roof, null, null, roof.roofMaterial)
|
||||
drawDirectionArrow(roof)
|
||||
canvas?.renderAll()
|
||||
changeSurfaceLineType(roof)
|
||||
closePopup(id)
|
||||
}
|
||||
|
||||
@ -105,6 +137,11 @@ export default function FlowDirectionSetting(props) {
|
||||
value={selectedOrientation}
|
||||
options={orientations}
|
||||
onChange={(e) => {
|
||||
if (e.value === '') {
|
||||
setCompasDeg(null)
|
||||
setSelectedOrientation(e)
|
||||
return
|
||||
}
|
||||
setType(FLOW_DIRECTION_TYPE.EIGHT_AZIMUTH)
|
||||
setSelectedOrientation(e)
|
||||
setCompasDeg(e.value)
|
||||
@ -133,31 +170,32 @@ export default function FlowDirectionSetting(props) {
|
||||
<div className="draw-flow-wrap">
|
||||
<div className="compas-box">
|
||||
<div className="compas-box-inner">
|
||||
{Array.from({ length: 180 / 15 + 1 }).map((dot, index) => (
|
||||
{Array.from({ length: 180 / 15 }).map((dot, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`circle ${compasDeg === 15 * (12 + index) && type === FLOW_DIRECTION_TYPE.TWENTY_FOUR_AZIMUTH ? 'act' : ''}`}
|
||||
className={`circle ${compasDeg === -15 * index + 180 ? 'act' : ''}`}
|
||||
onClick={() => {
|
||||
if (index === 0) {
|
||||
setCompasDeg(180)
|
||||
return
|
||||
}
|
||||
setType(FLOW_DIRECTION_TYPE.TWENTY_FOUR_AZIMUTH)
|
||||
setCompasDeg(15 * (12 + index))
|
||||
setCompasDeg(-15 * index + 180)
|
||||
}}
|
||||
></div>
|
||||
))}
|
||||
{Array.from({ length: 180 / 15 - 1 }).map((dot, index) => (
|
||||
{Array.from({ length: 180 / 15 }).map((dot, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`circle ${compasDeg === 15 * (index + 1) && type === FLOW_DIRECTION_TYPE.TWENTY_FOUR_AZIMUTH ? 'act' : ''}`}
|
||||
className={`circle ${compasDeg === -1 * 15 * index ? 'act' : ''}`}
|
||||
onClick={() => {
|
||||
setType(FLOW_DIRECTION_TYPE.TWENTY_FOUR_AZIMUTH)
|
||||
setCompasDeg(15 * (index + 1))
|
||||
setCompasDeg(15 * index * -1)
|
||||
}}
|
||||
></div>
|
||||
))}
|
||||
<div className="compas">
|
||||
<div
|
||||
className="compas-arr"
|
||||
style={{ transform: `${type === FLOW_DIRECTION_TYPE.TWENTY_FOUR_AZIMUTH && `rotate(${compasDeg}deg)`}` }}
|
||||
></div>
|
||||
<div className="compas-arr" style={{ transform: `${`rotate(${-1 * compasDeg}deg)`}` }}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -4,9 +4,11 @@ import { usePopup } from '@/hooks/usePopup'
|
||||
import { useRecoilValue } from 'recoil'
|
||||
import { contextPopupPositionState } from '@/store/popupAtom'
|
||||
import { useState } from 'react'
|
||||
import { currentObjectState } from '@/store/canvasAtom'
|
||||
import { useGrid } from '@/hooks/common/useGrid'
|
||||
import { canvasState, currentObjectState } from '@/store/canvasAtom'
|
||||
import { gridColorState } from '@/store/gridAtom'
|
||||
import { gridDisplaySelector } from '@/store/settingAtom'
|
||||
|
||||
const GRID_PADDING = 5
|
||||
export default function GridCopy(props) {
|
||||
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
|
||||
const { id, pos = contextPopupPosition } = props
|
||||
@ -15,9 +17,39 @@ export default function GridCopy(props) {
|
||||
const [length, setLength] = useState('0')
|
||||
const [arrow, setArrow] = useState(null)
|
||||
const currentObject = useRecoilValue(currentObjectState)
|
||||
const { copy } = useGrid()
|
||||
const canvas = useRecoilValue(canvasState)
|
||||
const gridColor = useRecoilValue(gridColorState)
|
||||
const isGridDisplay = useRecoilValue(gridDisplaySelector)
|
||||
const handleApply = () => {
|
||||
copy(currentObject, ['↑', '←'].includes(arrow) ? +length * -1 : +length)
|
||||
copy(currentObject, ['↑', '←'].includes(arrow) ? (+length * -1) / 10 : +length / 10)
|
||||
}
|
||||
|
||||
const copy = (object, length) => {
|
||||
const lineStartX = object.direction === 'vertical' ? object.x1 + length : object.x1
|
||||
const lineEndX = object.direction === 'vertical' ? object.x2 + length : object.x2
|
||||
const lineStartY = object.direction === 'vertical' ? object.y1 : object.y1 + length
|
||||
const lineEndY = object.direction === 'vertical' ? object.y2 : object.y1 + length
|
||||
|
||||
const line = new fabric.Line([lineStartX, lineStartY, lineEndX, lineEndY], {
|
||||
stroke: gridColor,
|
||||
strokeWidth: 1,
|
||||
selectable: true,
|
||||
lockMovementX: true,
|
||||
lockMovementY: true,
|
||||
lockRotation: true,
|
||||
lockScalingX: true,
|
||||
lockScalingY: true,
|
||||
strokeDashArray: [5, 2],
|
||||
opacity: 0.3,
|
||||
padding: GRID_PADDING,
|
||||
direction: object.direction,
|
||||
visible: isGridDisplay,
|
||||
name: object.name,
|
||||
})
|
||||
|
||||
canvas.add(line)
|
||||
canvas.setActiveObject(line)
|
||||
canvas.renderAll()
|
||||
}
|
||||
return (
|
||||
<WithDraggable isShow={true} pos={pos} className="xm">
|
||||
|
||||
@ -3,12 +3,10 @@ import { useMessage } from '@/hooks/useMessage'
|
||||
import { usePopup } from '@/hooks/usePopup'
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
import { contextPopupPositionState } from '@/store/popupAtom'
|
||||
import { useCanvas } from '@/hooks/useCanvas'
|
||||
import { canvasState, currentObjectState } from '@/store/canvasAtom'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useGrid } from '@/hooks/common/useGrid'
|
||||
import { useSwal } from '@/hooks/useSwal'
|
||||
import { set } from 'react-hook-form'
|
||||
import { normalizeDigits } from '@/util/input-utils'
|
||||
|
||||
export default function GridMove(props) {
|
||||
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
|
||||
@ -17,7 +15,6 @@ export default function GridMove(props) {
|
||||
const { getMessage } = useMessage()
|
||||
const { closePopup } = usePopup()
|
||||
const { swalFire } = useSwal()
|
||||
const { move } = useGrid()
|
||||
const [currentObject, setCurrentObject] = useRecoilState(currentObjectState)
|
||||
const [isAll, setIsAll] = useState(false)
|
||||
const [verticalSize, setVerticalSize] = useState('0')
|
||||
@ -54,21 +51,31 @@ export default function GridMove(props) {
|
||||
.forEach((grid) => {
|
||||
move(
|
||||
grid,
|
||||
arrow2 === '←' ? Number(horizonSize) * -1 : Number(horizonSize),
|
||||
arrow1 === '↑' ? Number(verticalSize) * -1 : Number(verticalSize),
|
||||
arrow2 === '←' ? (Number(normalizeDigits(horizonSize)) * -1) / 10 : Number(normalizeDigits(horizonSize)) / 10,
|
||||
arrow1 === '↑' ? (Number(normalizeDigits(verticalSize)) * -1) / 10 : Number(normalizeDigits(verticalSize)) / 10,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
move(
|
||||
currentObject,
|
||||
arrow2 === '←' ? Number(horizonSize) * -1 : Number(horizonSize),
|
||||
arrow1 === '↑' ? Number(verticalSize) * -1 : Number(verticalSize),
|
||||
arrow2 === '←' ? (Number(normalizeDigits(horizonSize)) * -1) / 10 : Number(normalizeDigits(horizonSize)) / 10,
|
||||
arrow1 === '↑' ? (Number(normalizeDigits(verticalSize)) * -1) / 10 : Number(normalizeDigits(verticalSize)) / 10,
|
||||
)
|
||||
}
|
||||
canvas.renderAll()
|
||||
handleClose()
|
||||
}
|
||||
|
||||
const move = (object, x, y) => {
|
||||
object.set({
|
||||
...object,
|
||||
x1: object.direction === 'vertical' ? object.x1 + x : object.x1,
|
||||
x2: object.direction === 'vertical' ? object.x1 + x : object.x2,
|
||||
y1: object.direction === 'vertical' ? object.y1 : object.y1 + y,
|
||||
y2: object.direction === 'vertical' ? object.y2 : object.y1 + y,
|
||||
})
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
closePopup(id)
|
||||
}
|
||||
@ -91,7 +98,7 @@ export default function GridMove(props) {
|
||||
type="text"
|
||||
className="input-origin"
|
||||
value={verticalSize}
|
||||
onChange={(e) => setVerticalSize(e.target.value)}
|
||||
onChange={(e) => setVerticalSize(normalizeDigits(e.target.value))}
|
||||
readOnly={!isAll && currentObject?.direction === 'vertical'}
|
||||
/>
|
||||
</div>
|
||||
@ -119,7 +126,7 @@ export default function GridMove(props) {
|
||||
type="text"
|
||||
className="input-origin"
|
||||
value={horizonSize}
|
||||
onChange={(e) => setHorizonSize(e.target.value)}
|
||||
onChange={(e) => setHorizonSize(normalizeDigits(e.target.value))}
|
||||
readOnly={!isAll && currentObject?.direction === 'horizontal'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"
|
||||
/>
|
||||
|
||||
@ -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"
|
||||
/>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||