Compare commits
9 Commits
7a6b9cbf92
...
6ec1d3fa9f
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ec1d3fa9f | |||
|
|
534e672ad5 | ||
|
|
c1641e167d | ||
|
|
fcd80cbe3b | ||
| 09795f71ac | |||
| d21865ca65 | |||
| 2d1184e1c0 | |||
| e2e5a484ca | |||
| f54260fc27 |
@ -10,8 +10,8 @@ NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120
|
|||||||
NEXT_PUBLIC_INQUIRY_API_URL=http://1.248.227.176:38080
|
NEXT_PUBLIC_INQUIRY_API_URL=http://1.248.227.176:38080
|
||||||
|
|
||||||
#QPARTNER 로그인 api
|
#QPARTNER 로그인 api
|
||||||
DB_HOST=asdf
|
#DB_HOST=202.218.61.226
|
||||||
DB_USER=asdf
|
#DB_USER=readonly
|
||||||
DB_PASSWORD=asdf
|
#DB_PASSWORD=aAjmFW12iHKW84l1
|
||||||
DB_DATABASE=asdf
|
#DB_DATABASE=qpartners
|
||||||
DB_PORT=3306
|
#DB_PORT=3306
|
||||||
@ -8,8 +8,8 @@ NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120
|
|||||||
NEXT_PUBLIC_INQUIRY_API_URL=http://1.248.227.176:38080
|
NEXT_PUBLIC_INQUIRY_API_URL=http://1.248.227.176:38080
|
||||||
|
|
||||||
#QPARTNER 로그인 api
|
#QPARTNER 로그인 api
|
||||||
DB_HOST=asdf
|
#DB_HOST=202.218.61.226
|
||||||
DB_USER=asdf
|
#DB_USER=readonly
|
||||||
DB_PASSWORD=asdf
|
#DB_PASSWORD=aAjmFW12iHKW84l1
|
||||||
DB_DATABASE=asdf
|
#DB_DATABASE=qpartners
|
||||||
DB_PORT=3306
|
#DB_PORT=3306
|
||||||
@ -16,6 +16,7 @@
|
|||||||
"iron-session": "^8.0.4",
|
"iron-session": "^8.0.4",
|
||||||
"lucide": "^0.503.0",
|
"lucide": "^0.503.0",
|
||||||
"mssql": "^11.0.1",
|
"mssql": "^11.0.1",
|
||||||
|
"mysql2": "^3.14.1",
|
||||||
"next": "15.2.4",
|
"next": "15.2.4",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
@ -27,6 +28,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/mysql": "^2.15.27",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
|
|||||||
89
pnpm-lock.yaml
generated
89
pnpm-lock.yaml
generated
@ -29,6 +29,9 @@ importers:
|
|||||||
mssql:
|
mssql:
|
||||||
specifier: ^11.0.1
|
specifier: ^11.0.1
|
||||||
version: 11.0.1
|
version: 11.0.1
|
||||||
|
mysql2:
|
||||||
|
specifier: ^3.14.1
|
||||||
|
version: 3.14.1
|
||||||
next:
|
next:
|
||||||
specifier: 15.2.4
|
specifier: 15.2.4
|
||||||
version: 15.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0)
|
version: 15.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0)
|
||||||
@ -57,6 +60,9 @@ importers:
|
|||||||
'@tailwindcss/postcss':
|
'@tailwindcss/postcss':
|
||||||
specifier: ^4
|
specifier: ^4
|
||||||
version: 4.0.17
|
version: 4.0.17
|
||||||
|
'@types/mysql':
|
||||||
|
specifier: ^2.15.27
|
||||||
|
version: 2.15.27
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20
|
specifier: ^20
|
||||||
version: 20.17.28
|
version: 20.17.28
|
||||||
@ -676,6 +682,9 @@ packages:
|
|||||||
'@tediousjs/connection-string@0.5.0':
|
'@tediousjs/connection-string@0.5.0':
|
||||||
resolution: {integrity: sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ==}
|
resolution: {integrity: sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ==}
|
||||||
|
|
||||||
|
'@types/mysql@2.15.27':
|
||||||
|
resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==}
|
||||||
|
|
||||||
'@types/node@20.17.28':
|
'@types/node@20.17.28':
|
||||||
resolution: {integrity: sha512-DHlH/fNL6Mho38jTy7/JT7sn2wnXI+wULR6PV4gy4VHLVvnrV/d3pHAMQHhc4gjdLmK2ZiPoMxzp6B3yRajLSQ==}
|
resolution: {integrity: sha512-DHlH/fNL6Mho38jTy7/JT7sn2wnXI+wULR6PV4gy4VHLVvnrV/d3pHAMQHhc4gjdLmK2ZiPoMxzp6B3yRajLSQ==}
|
||||||
|
|
||||||
@ -712,6 +721,10 @@ packages:
|
|||||||
engines: {node: '>= 4.5.0'}
|
engines: {node: '>= 4.5.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
aws-ssl-profiles@1.1.2:
|
||||||
|
resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==}
|
||||||
|
engines: {node: '>= 6.0.0'}
|
||||||
|
|
||||||
axios@1.8.4:
|
axios@1.8.4:
|
||||||
resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==}
|
resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==}
|
||||||
|
|
||||||
@ -826,6 +839,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
|
denque@2.1.0:
|
||||||
|
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
|
||||||
|
engines: {node: '>=0.10'}
|
||||||
|
|
||||||
detect-libc@1.0.3:
|
detect-libc@1.0.3:
|
||||||
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
|
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
|
||||||
engines: {node: '>=0.10'}
|
engines: {node: '>=0.10'}
|
||||||
@ -911,6 +928,9 @@ packages:
|
|||||||
function-bind@1.1.2:
|
function-bind@1.1.2:
|
||||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||||
|
|
||||||
|
generate-function@2.3.1:
|
||||||
|
resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==}
|
||||||
|
|
||||||
get-intrinsic@1.3.0:
|
get-intrinsic@1.3.0:
|
||||||
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -994,6 +1014,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||||
engines: {node: '>=0.12.0'}
|
engines: {node: '>=0.12.0'}
|
||||||
|
|
||||||
|
is-property@1.0.2:
|
||||||
|
resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==}
|
||||||
|
|
||||||
is-wsl@3.1.0:
|
is-wsl@3.1.0:
|
||||||
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
|
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
@ -1112,6 +1135,17 @@ packages:
|
|||||||
lodash.once@4.1.1:
|
lodash.once@4.1.1:
|
||||||
resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
|
resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
|
||||||
|
|
||||||
|
long@5.3.2:
|
||||||
|
resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
|
||||||
|
|
||||||
|
lru-cache@7.18.3:
|
||||||
|
resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
lru.min@1.1.2:
|
||||||
|
resolution: {integrity: sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==}
|
||||||
|
engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'}
|
||||||
|
|
||||||
lucide@0.503.0:
|
lucide@0.503.0:
|
||||||
resolution: {integrity: sha512-ZAVlxBU4dbSUAVidb2eT0fH3bTtKCj7M2aZNAVsFOrcnazvYJFu6I8OxFE+Fmx5XNf22Cw4Ln3NBHfBxNfoFOw==}
|
resolution: {integrity: sha512-ZAVlxBU4dbSUAVidb2eT0fH3bTtKCj7M2aZNAVsFOrcnazvYJFu6I8OxFE+Fmx5XNf22Cw4Ln3NBHfBxNfoFOw==}
|
||||||
|
|
||||||
@ -1139,6 +1173,14 @@ packages:
|
|||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
mysql2@3.14.1:
|
||||||
|
resolution: {integrity: sha512-7ytuPQJjQB8TNAYX/H2yhL+iQOnIBjAMam361R7UAL0lOVXWjtdrmoL9HYKqKoLp/8UUTRcvo1QPvK9KL7wA8w==}
|
||||||
|
engines: {node: '>= 8.0'}
|
||||||
|
|
||||||
|
named-placeholders@1.1.3:
|
||||||
|
resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
nanoid@3.3.11:
|
nanoid@3.3.11:
|
||||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
@ -1275,6 +1317,9 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
seq-queue@0.0.5:
|
||||||
|
resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==}
|
||||||
|
|
||||||
sharp@0.33.5:
|
sharp@0.33.5:
|
||||||
resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
|
resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
@ -1289,6 +1334,10 @@ packages:
|
|||||||
sprintf-js@1.1.3:
|
sprintf-js@1.1.3:
|
||||||
resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
|
resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
|
||||||
|
|
||||||
|
sqlstring@2.3.3:
|
||||||
|
resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
stackblur-canvas@2.7.0:
|
stackblur-canvas@2.7.0:
|
||||||
resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==}
|
resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==}
|
||||||
engines: {node: '>=0.1.14'}
|
engines: {node: '>=0.1.14'}
|
||||||
@ -1890,6 +1939,10 @@ snapshots:
|
|||||||
|
|
||||||
'@tediousjs/connection-string@0.5.0': {}
|
'@tediousjs/connection-string@0.5.0': {}
|
||||||
|
|
||||||
|
'@types/mysql@2.15.27':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 20.17.28
|
||||||
|
|
||||||
'@types/node@20.17.28':
|
'@types/node@20.17.28':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.19.8
|
undici-types: 6.19.8
|
||||||
@ -1923,6 +1976,8 @@ snapshots:
|
|||||||
|
|
||||||
atob@2.1.2: {}
|
atob@2.1.2: {}
|
||||||
|
|
||||||
|
aws-ssl-profiles@1.1.2: {}
|
||||||
|
|
||||||
axios@1.8.4:
|
axios@1.8.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects: 1.15.9
|
follow-redirects: 1.15.9
|
||||||
@ -2041,6 +2096,8 @@ snapshots:
|
|||||||
|
|
||||||
delayed-stream@1.0.0: {}
|
delayed-stream@1.0.0: {}
|
||||||
|
|
||||||
|
denque@2.1.0: {}
|
||||||
|
|
||||||
detect-libc@1.0.3:
|
detect-libc@1.0.3:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@ -2141,6 +2198,10 @@ snapshots:
|
|||||||
|
|
||||||
function-bind@1.1.2: {}
|
function-bind@1.1.2: {}
|
||||||
|
|
||||||
|
generate-function@2.3.1:
|
||||||
|
dependencies:
|
||||||
|
is-property: 1.0.2
|
||||||
|
|
||||||
get-intrinsic@1.3.0:
|
get-intrinsic@1.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind-apply-helpers: 1.0.2
|
call-bind-apply-helpers: 1.0.2
|
||||||
@ -2230,6 +2291,8 @@ snapshots:
|
|||||||
is-number@7.0.0:
|
is-number@7.0.0:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
is-property@1.0.2: {}
|
||||||
|
|
||||||
is-wsl@3.1.0:
|
is-wsl@3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-inside-container: 1.0.0
|
is-inside-container: 1.0.0
|
||||||
@ -2346,6 +2409,12 @@ snapshots:
|
|||||||
|
|
||||||
lodash.once@4.1.1: {}
|
lodash.once@4.1.1: {}
|
||||||
|
|
||||||
|
long@5.3.2: {}
|
||||||
|
|
||||||
|
lru-cache@7.18.3: {}
|
||||||
|
|
||||||
|
lru.min@1.1.2: {}
|
||||||
|
|
||||||
lucide@0.503.0: {}
|
lucide@0.503.0: {}
|
||||||
|
|
||||||
math-intrinsics@1.1.0: {}
|
math-intrinsics@1.1.0: {}
|
||||||
@ -2375,6 +2444,22 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
mysql2@3.14.1:
|
||||||
|
dependencies:
|
||||||
|
aws-ssl-profiles: 1.1.2
|
||||||
|
denque: 2.1.0
|
||||||
|
generate-function: 2.3.1
|
||||||
|
iconv-lite: 0.6.3
|
||||||
|
long: 5.3.2
|
||||||
|
lru.min: 1.1.2
|
||||||
|
named-placeholders: 1.1.3
|
||||||
|
seq-queue: 0.0.5
|
||||||
|
sqlstring: 2.3.3
|
||||||
|
|
||||||
|
named-placeholders@1.1.3:
|
||||||
|
dependencies:
|
||||||
|
lru-cache: 7.18.3
|
||||||
|
|
||||||
nanoid@3.3.11: {}
|
nanoid@3.3.11: {}
|
||||||
|
|
||||||
native-duplexpair@1.0.0: {}
|
native-duplexpair@1.0.0: {}
|
||||||
@ -2508,6 +2593,8 @@ snapshots:
|
|||||||
|
|
||||||
semver@7.7.1: {}
|
semver@7.7.1: {}
|
||||||
|
|
||||||
|
seq-queue@0.0.5: {}
|
||||||
|
|
||||||
sharp@0.33.5:
|
sharp@0.33.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
color: 4.2.3
|
color: 4.2.3
|
||||||
@ -2544,6 +2631,8 @@ snapshots:
|
|||||||
|
|
||||||
sprintf-js@1.1.3: {}
|
sprintf-js@1.1.3: {}
|
||||||
|
|
||||||
|
sqlstring@2.3.3: {}
|
||||||
|
|
||||||
stackblur-canvas@2.7.0:
|
stackblur-canvas@2.7.0:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
|||||||
49
src/app/api/partner/route.ts
Normal file
49
src/app/api/partner/route.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { NextResponse } from 'next/server'
|
||||||
|
import executeQuery from '@/libs/partner'
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
// const sqls = `SELECT
|
||||||
|
// r.data_id,
|
||||||
|
// u.id AS user_id,
|
||||||
|
// u.login_id AS user_login_id,
|
||||||
|
// u.password AS user_password,
|
||||||
|
// u.user_name AS user_name,
|
||||||
|
// u.user_name_kana AS user_name_kana,
|
||||||
|
// u.sei AS user_sei,
|
||||||
|
// u.mei AS user_mei,
|
||||||
|
// u.sei_kana AS user_sei_kana,
|
||||||
|
// u.mei_kana AS user_mei_kana,
|
||||||
|
// u.user_tel AS user_tel,
|
||||||
|
// u.user_fax AS user_fax,
|
||||||
|
// u.status AS user_status,
|
||||||
|
// u.seko_id AS user_seko_id,
|
||||||
|
// u.seko_limit AS user_seko_limit,
|
||||||
|
// s.id AS supplier_id,
|
||||||
|
// s.code AS supplier_code,
|
||||||
|
// s.name AS supplier_name,
|
||||||
|
// s.name_kana AS supplier_name_kana,
|
||||||
|
// s.kind AS supplier_kind
|
||||||
|
// FROM
|
||||||
|
// R_DATA r
|
||||||
|
// JOIN
|
||||||
|
// M_USER u ON r.data_id = u.id
|
||||||
|
// JOIN
|
||||||
|
// M_SUPPLIER s ON r.relation_id = s.id
|
||||||
|
// WHERE
|
||||||
|
// u.status = '1'
|
||||||
|
// AND
|
||||||
|
// u.seko_id is not null
|
||||||
|
// AND
|
||||||
|
// u.seko_limit > now()
|
||||||
|
// AND
|
||||||
|
// s.kind = '4'
|
||||||
|
// AND
|
||||||
|
// u.login_id = ?
|
||||||
|
// AND
|
||||||
|
// u.password = ?
|
||||||
|
// `
|
||||||
|
const sql = 'SELECT * FROM M_USER'
|
||||||
|
const data = await executeQuery(sql, [])
|
||||||
|
console.log('🚀 ~ GET ~ data:', data)
|
||||||
|
return NextResponse.json(data)
|
||||||
|
}
|
||||||
@ -1,82 +1,75 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { prisma } from '@/libs/prisma'
|
import { prisma } from '@/libs/prisma'
|
||||||
import { SUITABLE_HEAD_CODE, type SuitableMain } from '@/types/Suitable'
|
import { type Suitable } from '@/types/Suitable'
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const searchParams = request.nextUrl.searchParams
|
const searchParams = request.nextUrl.searchParams
|
||||||
|
|
||||||
|
const pageNumber = parseInt(searchParams.get('pageNumber') || '0')
|
||||||
|
const itemPerPage = parseInt(searchParams.get('itemPerPage') || '0')
|
||||||
|
if (pageNumber === 0 || itemPerPage === 0) {
|
||||||
|
return NextResponse.json({ error: '페이지 번호와 페이지당 아이템 수가 필요합니다' }, { status: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const ids = searchParams.get('ids')
|
||||||
const category = searchParams.get('category')
|
const category = searchParams.get('category')
|
||||||
const keyword = searchParams.get('keyword')
|
const keyword = searchParams.get('keyword')
|
||||||
|
|
||||||
let MainWhereCondition: any = {}
|
let query = `
|
||||||
const whereCondition: string[] = []
|
SELECT
|
||||||
const params: string[] = []
|
msm.id
|
||||||
|
, msm.product_name
|
||||||
|
, msm.manu_ft_cd
|
||||||
|
, msm.roof_mt_cd
|
||||||
|
, msm.roof_sh_cd
|
||||||
|
, details.detail
|
||||||
|
FROM ms_suitable_main msm
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
msd.main_id
|
||||||
|
, (
|
||||||
|
SELECT
|
||||||
|
msd_json.id
|
||||||
|
, msd_json.trestle_mfpc_cd
|
||||||
|
, msd_json.trestle_manufacturer_product_name
|
||||||
|
, msd_json.memo
|
||||||
|
FROM ms_suitable_detail msd_json
|
||||||
|
WHERE msd.main_id = msd_json.main_id
|
||||||
|
FOR JSON PATH
|
||||||
|
) AS detail
|
||||||
|
FROM ms_suitable_detail msd
|
||||||
|
GROUP BY msd.main_id
|
||||||
|
) AS details
|
||||||
|
ON msm.id = details.main_id
|
||||||
|
--mainIds AND details.main_id IN (:mainIds)
|
||||||
|
WHERE 1=1
|
||||||
|
--mainIds AND msm.id IN (:mainIds)
|
||||||
|
--roofMtCd AND msm.roof_mt_cd = ':roofMtCd'
|
||||||
|
--productName AND msm.product_name LIKE '%:productName%'
|
||||||
|
ORDER BY msm.product_name
|
||||||
|
OFFSET (@P1 - 1) * @P2 ROWS
|
||||||
|
FETCH NEXT @P2 ROWS ONLY;
|
||||||
|
`
|
||||||
|
|
||||||
|
// 검색 조건 설정
|
||||||
|
if (ids) {
|
||||||
|
query = query.replaceAll('--mainIds ', '')
|
||||||
|
query = query.replaceAll(':mainIds', ids)
|
||||||
|
}
|
||||||
if (category) {
|
if (category) {
|
||||||
whereCondition.push(`${SUITABLE_HEAD_CODE.ROOF_MT_CD} = @P1`)
|
query = query.replace('--roofMtCd ', '')
|
||||||
params.push(category)
|
query = query.replace(':roofMtCd', category)
|
||||||
MainWhereCondition[SUITABLE_HEAD_CODE.ROOF_MT_CD] = category
|
|
||||||
}
|
}
|
||||||
if (keyword) {
|
if (keyword) {
|
||||||
whereCondition.push('PRODUCT_NAME LIKE @P2')
|
query = query.replace('--productName ', '')
|
||||||
params.push(`%${keyword}%`)
|
query = query.replace(':productName', keyword)
|
||||||
MainWhereCondition['PRODUCT_NAME'] = {
|
|
||||||
contains: keyword,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const startTime = performance.now()
|
|
||||||
console.log(`쿼리 (main table) 시작 시간: ${startTime}ms`)
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const suitable = await prisma.MS_SUITABLE_MAIN.findMany({
|
const suitable: Suitable[] = await prisma.$queryRawUnsafe(query, pageNumber, itemPerPage)
|
||||||
select: {
|
|
||||||
ID: true,
|
|
||||||
PRODUCT_NAME: true,
|
|
||||||
ROOF_MT_CD: true,
|
|
||||||
},
|
|
||||||
where: MainWhereCondition,
|
|
||||||
orderBy: {
|
|
||||||
PRODUCT_NAME: 'asc',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const endTime = performance.now()
|
return NextResponse.json(suitable)
|
||||||
console.log(`쿼리 (main table) 종료 시간: ${endTime - startTime}ms`)
|
|
||||||
|
|
||||||
const mainIds: number[] = suitable.map((item: SuitableMain) => item.id)
|
|
||||||
|
|
||||||
|
|
||||||
const startTime2 = performance.now()
|
|
||||||
console.log(`쿼리 (detail table) 시작 시간: ${startTime2}ms`)
|
|
||||||
let detailQuery = `
|
|
||||||
SELECT
|
|
||||||
msd.main_id
|
|
||||||
, (
|
|
||||||
SELECT
|
|
||||||
msd_json.id
|
|
||||||
, msd_json.trestle_mfpc_cd
|
|
||||||
, msd_json.trestle_manufacturer_product_name
|
|
||||||
, msd_json.memo
|
|
||||||
FROM ms_suitable_detail msd_json
|
|
||||||
WHERE msd.main_id = msd_json.main_id
|
|
||||||
FOR JSON PATH
|
|
||||||
) AS detail
|
|
||||||
FROM ms_suitable_detail msd
|
|
||||||
-- WHERE 1=1
|
|
||||||
GROUP BY msd.main_id
|
|
||||||
`
|
|
||||||
if (whereCondition.length > 0) {
|
|
||||||
detailQuery = detailQuery.replace('-- WHERE 1=1', `WHERE msd.main_id IN @P1`)
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
const detail = await prisma.$queryRawUnsafe(detailQuery, ...mainIds)
|
|
||||||
|
|
||||||
const endTime2 = performance.now()
|
|
||||||
console.log(`쿼리 (detail table) 종료 시간: ${endTime2 - startTime2}ms`)
|
|
||||||
|
|
||||||
const endTime3 = performance.now()
|
|
||||||
console.log(`쿼리 총 실행 시간: ${endTime3 - startTime}ms`)
|
|
||||||
|
|
||||||
return NextResponse.json({ suitable, detail })
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 데이터 조회 중 오류가 발생했습니다:', error)
|
console.error('❌ 데이터 조회 중 오류가 발생했습니다:', error)
|
||||||
return NextResponse.json({ error: '데이터 조회 중 오류가 발생했습니다' }, { status: 500 })
|
return NextResponse.json({ error: '데이터 조회 중 오류가 발생했습니다' }, { status: 500 })
|
||||||
|
|||||||
@ -1,71 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { prisma } from '@/libs/prisma'
|
|
||||||
import { SUITABLE_HEAD_CODE, type Suitable } from '@/types/Suitable'
|
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
|
||||||
try {
|
|
||||||
const searchParams = request.nextUrl.searchParams
|
|
||||||
const category = searchParams.get('category')
|
|
||||||
const keyword = searchParams.get('keyword')
|
|
||||||
|
|
||||||
const whereCondition: string[] = []
|
|
||||||
const params: string[] = []
|
|
||||||
if (category) {
|
|
||||||
whereCondition.push(`${SUITABLE_HEAD_CODE.ROOF_MT_CD} = @P1`)
|
|
||||||
params.push(category)
|
|
||||||
}
|
|
||||||
if (keyword) {
|
|
||||||
whereCondition.push('PRODUCT_NAME LIKE @P2')
|
|
||||||
params.push(`%${keyword}%`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const startTime = performance.now()
|
|
||||||
console.log(`쿼리 시작 시간: ${startTime}ms`)
|
|
||||||
|
|
||||||
let query = `
|
|
||||||
SELECT
|
|
||||||
msm.id
|
|
||||||
, msm.product_name
|
|
||||||
, msm.manu_ft_cd
|
|
||||||
, msm.roof_mt_cd
|
|
||||||
, msm.roof_sh_cd
|
|
||||||
, details.detail
|
|
||||||
FROM ms_suitable_main msm
|
|
||||||
LEFT JOIN (
|
|
||||||
SELECT
|
|
||||||
msd.main_id
|
|
||||||
, (
|
|
||||||
SELECT
|
|
||||||
msd_json.id
|
|
||||||
, msd_json.trestle_mfpc_cd
|
|
||||||
, msd_json.trestle_manufacturer_product_name
|
|
||||||
, msd_json.memo
|
|
||||||
FROM ms_suitable_detail msd_json
|
|
||||||
WHERE msd.main_id = msd_json.main_id
|
|
||||||
FOR JSON PATH
|
|
||||||
) AS detail
|
|
||||||
FROM ms_suitable_detail msd
|
|
||||||
GROUP BY msd.main_id
|
|
||||||
) AS details
|
|
||||||
ON msm.id = details.main_id
|
|
||||||
-- AND details.main_id IN (#mainIds)
|
|
||||||
-- WHERE 1=1
|
|
||||||
ORDER BY msm.product_name`
|
|
||||||
|
|
||||||
// 검색 조건 추가
|
|
||||||
if (whereCondition.length > 0) {
|
|
||||||
query = query.replace('-- WHERE 1=1', `WHERE ${whereCondition.join(' AND ')}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const suitable: Suitable[] = await prisma.$queryRawUnsafe(query, ...params)
|
|
||||||
|
|
||||||
const endTime = performance.now()
|
|
||||||
console.log(`쿼리 실행 시간: ${endTime - startTime}ms`)
|
|
||||||
|
|
||||||
return NextResponse.json(suitable)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ 데이터 조회 중 오류가 발생했습니다:', error)
|
|
||||||
return NextResponse.json({ error: '데이터 조회 중 오류가 발생했습니다' }, { status: 500 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,9 +1,10 @@
|
|||||||
import { NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { prisma } from '@/libs/prisma'
|
import { prisma } from '@/libs/prisma'
|
||||||
|
import { convertToSnakeCase } from '@/utils/common-utils'
|
||||||
|
|
||||||
export async function GET(request: Request, context: { params: { id: string } }) {
|
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||||
try {
|
try {
|
||||||
const { id } = await context.params
|
const { id } = await params
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.findUnique({
|
const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.findUnique({
|
||||||
where: { ID: Number(id) },
|
where: { ID: Number(id) },
|
||||||
@ -18,34 +19,34 @@ export async function GET(request: Request, context: { params: { id: string } })
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function PUT(request: Request, context: { params: { id: string } }) {
|
export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||||
try {
|
try {
|
||||||
const { id } = await context.params
|
const { id } = await params
|
||||||
const body = await request.json()
|
const body = await request.json()
|
||||||
console.log('body:: ', body)
|
|
||||||
|
|
||||||
// DETAIL_INFO를 분리
|
|
||||||
const { DETAIL_INFO, ...basicInfo } = body
|
const { DETAIL_INFO, ...basicInfo } = body
|
||||||
|
|
||||||
|
console.log('body:: ', body)
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({
|
const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({
|
||||||
where: { ID: Number(id) },
|
where: { ID: Number(id) },
|
||||||
data: {
|
data: {
|
||||||
...basicInfo,
|
...convertToSnakeCase(basicInfo),
|
||||||
UPT_DT: new Date(),
|
UPT_DT: new Date(),
|
||||||
DETAIL_INFO: DETAIL_INFO
|
DETAIL_INFO: DETAIL_INFO ? {
|
||||||
? {
|
upsert: {
|
||||||
upsert: {
|
create: convertToSnakeCase(DETAIL_INFO),
|
||||||
create: DETAIL_INFO,
|
update: convertToSnakeCase(DETAIL_INFO),
|
||||||
update: DETAIL_INFO,
|
where: {
|
||||||
},
|
BASIC_INFO_ID: Number(id)
|
||||||
}
|
}
|
||||||
: undefined,
|
}
|
||||||
|
} : undefined
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
DETAIL_INFO: true,
|
DETAIL_INFO: true
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
console.log('survey:: ', survey)
|
||||||
return NextResponse.json(survey)
|
return NextResponse.json(survey)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating survey:', error)
|
console.error('Error updating survey:', error)
|
||||||
@ -53,9 +54,9 @@ export async function PUT(request: Request, context: { params: { id: string } })
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function DELETE(request: Request, context: { params: { id: string } }) {
|
export async function DELETE(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||||
try {
|
try {
|
||||||
const { id } = await context.params
|
const { id } = await params
|
||||||
|
|
||||||
await prisma.$transaction(async (tx) => {
|
await prisma.$transaction(async (tx) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -86,9 +87,9 @@ export async function DELETE(request: Request, context: { params: { id: string }
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function PATCH(request: Request, context: { params: { id: string } }) {
|
export async function PATCH(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||||
try {
|
try {
|
||||||
const { id } = await context.params
|
const { id } = await params
|
||||||
const body = await request.json()
|
const body = await request.json()
|
||||||
|
|
||||||
if (body.submit) {
|
if (body.submit) {
|
||||||
@ -98,40 +99,42 @@ export async function PATCH(request: Request, context: { params: { id: string }
|
|||||||
data: {
|
data: {
|
||||||
SUBMISSION_STATUS: true,
|
SUBMISSION_STATUS: true,
|
||||||
SUBMISSION_DATE: new Date(),
|
SUBMISSION_DATE: new Date(),
|
||||||
|
UPT_DT: new Date(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return NextResponse.json({ message: 'Survey confirmed successfully' })
|
return NextResponse.json({ message: 'Survey confirmed successfully' })
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
|
||||||
const hasDetails = await prisma.SD_SURVEY_SALES_DETAIL_INFO.findUnique({
|
|
||||||
where: { BASIC_INFO_ID: Number(id) },
|
|
||||||
})
|
|
||||||
|
|
||||||
if (hasDetails) {
|
|
||||||
//@ts-ignore
|
|
||||||
const result = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({
|
|
||||||
where: { ID: Number(id) },
|
|
||||||
data: {
|
|
||||||
UPT_DT: new Date(),
|
|
||||||
DETAIL_INFO: {
|
|
||||||
update: body.DETAIL_INFO,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return NextResponse.json(result)
|
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
|
||||||
const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({
|
|
||||||
where: { ID: Number(id) },
|
|
||||||
data: {
|
|
||||||
DETAIL_INFO: {
|
|
||||||
create: body.DETAIL_INFO,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return NextResponse.json({ message: 'Survey detail created successfully' })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// } else {
|
||||||
|
// // @ts-ignore
|
||||||
|
// const hasDetails = await prisma.SD_SURVEY_SALES_DETAIL_INFO.findUnique({
|
||||||
|
// where: { BASIC_INFO_ID: Number(id) },
|
||||||
|
// })
|
||||||
|
|
||||||
|
// if (hasDetails) {
|
||||||
|
// //@ts-ignore
|
||||||
|
// const result = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({
|
||||||
|
// where: { ID: Number(id) },
|
||||||
|
// data: {
|
||||||
|
// UPT_DT: new Date(),
|
||||||
|
// DETAIL_INFO: {
|
||||||
|
// update: convertToSnakeCase(body.DETAIL_INFO),
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
// return NextResponse.json(result)
|
||||||
|
// } else {
|
||||||
|
// // @ts-ignore
|
||||||
|
// const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({
|
||||||
|
// where: { ID: Number(id) },
|
||||||
|
// data: {
|
||||||
|
// DETAIL_INFO: {
|
||||||
|
// create: convertToSnakeCase(body.DETAIL_INFO),
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
// return NextResponse.json({ message: 'Survey detail created successfully' })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating survey:', error)
|
console.error('Error updating survey:', error)
|
||||||
return NextResponse.json({ error: 'Failed to update survey' }, { status: 500 })
|
return NextResponse.json({ error: 'Failed to update survey' }, { status: 500 })
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from 'next/server'
|
||||||
import { prisma } from '@/libs/prisma'
|
import { prisma } from '@/libs/prisma'
|
||||||
|
import { convertToSnakeCase } from '@/utils/common-utils'
|
||||||
/**
|
/**
|
||||||
* 검색 파라미터
|
* 검색 파라미터
|
||||||
*/
|
*/
|
||||||
@ -222,16 +222,19 @@ export async function PUT(request: Request) {
|
|||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
try {
|
try {
|
||||||
const body = await request.json()
|
const body = await request.json()
|
||||||
const { DETAIL_INFO, ...basicInfo } = body
|
console.log('body:: ', body)
|
||||||
|
|
||||||
|
const { detailInfo, ...basicInfo } = body
|
||||||
|
|
||||||
// 기본 정보 생성
|
// 기본 정보 생성
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const result = await prisma.SD_SURVEY_SALES_BASIC_INFO.create({
|
const result = await prisma.SD_SURVEY_SALES_BASIC_INFO.create({
|
||||||
data: {
|
data: {
|
||||||
...basicInfo,
|
...convertToSnakeCase(basicInfo),
|
||||||
DETAIL_INFO: {
|
DETAIL_INFO: {
|
||||||
create: DETAIL_INFO,
|
create: convertToSnakeCase(detailInfo)
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
return NextResponse.json(result)
|
return NextResponse.json(result)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -22,7 +22,7 @@ interface RootLayoutProps {
|
|||||||
header: ReactNode
|
header: ReactNode
|
||||||
footer: ReactNode
|
footer: ReactNode
|
||||||
floatBtn: ReactNode
|
floatBtn: ReactNode
|
||||||
}6
|
}
|
||||||
|
|
||||||
export default async function RootLayout({ children, header, footer, floatBtn }: RootLayoutProps): Promise<ReactNode> {
|
export default async function RootLayout({ children, header, footer, floatBtn }: RootLayoutProps): Promise<ReactNode> {
|
||||||
const cookieStore = await cookies()
|
const cookieStore = await cookies()
|
||||||
|
|||||||
@ -1,24 +0,0 @@
|
|||||||
import type { ReactNode } from 'react'
|
|
||||||
|
|
||||||
interface SuitableLayoutProps {
|
|
||||||
children: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function layout({ children }: SuitableLayoutProps) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="container">
|
|
||||||
<div className="sale-contents">
|
|
||||||
<div className="border-frame">
|
|
||||||
<div className="pw-guide">
|
|
||||||
<div className="pw-guide-txt">この適合表は参考資料として使用してください.</div>
|
|
||||||
<div className="pw-guide-txt">詳細やお問い合わせは1:1お問い合わせをご利用ください.</div>
|
|
||||||
<div className="pw-guide-txt">屋根材の選択or屋根材名を直接入力してください.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
import SuitableRaw from '@/components/suitable/SuitableRaw'
|
|
||||||
|
|
||||||
export default function page() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SuitableRaw />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -4,7 +4,6 @@ export default function page() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DataTable />
|
<DataTable />
|
||||||
{/* <DetailForm surveyInfo={surveyInfo} mode="READ" /> */}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import RegistForm from '@/components/survey-sale/detail/RegistForm'
|
import DetailForm from "@/components/survey-sale/detail/DetailForm";
|
||||||
|
|
||||||
export default function RegistPage() {
|
export default function RegistPage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RegistForm/>
|
<DetailForm />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import SuitableList from './SuitableList'
|
import SuitableListRaw from './SuitableList'
|
||||||
import { useSuitable } from '@/hooks/useSuitable'
|
import { useSuitable } from '@/hooks/useSuitable'
|
||||||
import { useSuitableStore } from '@/store/useSuitableStore'
|
import { useSuitableStore } from '@/store/useSuitableStore'
|
||||||
import type { CommCode } from '@/types/CommCode'
|
import type { CommCode } from '@/types/CommCode'
|
||||||
import { SUITABLE_HEAD_CODE } from '@/types/Suitable'
|
import { SUITABLE_HEAD_CODE } from '@/types/Suitable'
|
||||||
|
|
||||||
export default function Suitable() {
|
export default function SuitableRaw() {
|
||||||
const [reference, setReference] = useState(true)
|
const [reference, setReference] = useState(true)
|
||||||
|
|
||||||
const { getSuitableCommCode, refetchBySearch } = useSuitable()
|
const { getSuitableCommCode, refetchBySearch } = useSuitable()
|
||||||
@ -110,7 +110,7 @@ export default function Suitable() {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<SuitableList />
|
<SuitableListRaw />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import SuitableButton from './SuitableButton'
|
|||||||
import SuitableNoData from './SuitableNoData'
|
import SuitableNoData from './SuitableNoData'
|
||||||
import { useSuitable } from '@/hooks/useSuitable'
|
import { useSuitable } from '@/hooks/useSuitable'
|
||||||
import { useSuitableStore } from '@/store/useSuitableStore'
|
import { useSuitableStore } from '@/store/useSuitableStore'
|
||||||
import { SUITABLE_HEAD_CODE, type SuitableMain, type SuitableDetail } from '@/types/Suitable'
|
import { SUITABLE_HEAD_CODE, type Suitable, type SuitableDetail } from '@/types/Suitable'
|
||||||
|
|
||||||
// 한 번에 로드할 아이템 수
|
// 한 번에 로드할 아이템 수
|
||||||
const ITEMS_PER_PAGE = 100
|
const ITEMS_PER_PAGE = 100
|
||||||
@ -14,9 +14,8 @@ const ITEMS_PER_PAGE = 100
|
|||||||
export default function SuitableList() {
|
export default function SuitableList() {
|
||||||
const { toCodeName, suitableSearchResults, isSearchLoading, toSuitableDetail } = useSuitable()
|
const { toCodeName, suitableSearchResults, isSearchLoading, toSuitableDetail } = useSuitable()
|
||||||
const { selectedItems, addSelectedItem, removeSelectedItem } = useSuitableStore()
|
const { selectedItems, addSelectedItem, removeSelectedItem } = useSuitableStore()
|
||||||
|
|
||||||
const [openItems, setOpenItems] = useState<Set<number>>(new Set())
|
const [openItems, setOpenItems] = useState<Set<number>>(new Set())
|
||||||
const [visibleItems, setVisibleItems] = useState<SuitableMain[]>([])
|
const [visibleItems, setVisibleItems] = useState<Suitable[]>([])
|
||||||
const [page, setPage] = useState(1)
|
const [page, setPage] = useState(1)
|
||||||
const [isLoadingMore, setIsLoadingMore] = useState(false)
|
const [isLoadingMore, setIsLoadingMore] = useState(false)
|
||||||
const observerTarget = useRef<HTMLDivElement>(null)
|
const observerTarget = useRef<HTMLDivElement>(null)
|
||||||
@ -32,7 +31,7 @@ export default function SuitableList() {
|
|||||||
// 초기 데이터 로드
|
// 초기 데이터 로드
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (suitableSearchResults) {
|
if (suitableSearchResults) {
|
||||||
const initialItems = suitableSearchResults.suitable.slice(0, ITEMS_PER_PAGE)
|
const initialItems = suitableSearchResults.slice(0, ITEMS_PER_PAGE)
|
||||||
setVisibleItems(initialItems)
|
setVisibleItems(initialItems)
|
||||||
setPage(1)
|
setPage(1)
|
||||||
}
|
}
|
||||||
@ -46,7 +45,7 @@ export default function SuitableList() {
|
|||||||
const nextPage = page + 1
|
const nextPage = page + 1
|
||||||
const startIndex = (nextPage - 1) * ITEMS_PER_PAGE
|
const startIndex = (nextPage - 1) * ITEMS_PER_PAGE
|
||||||
const endIndex = startIndex + ITEMS_PER_PAGE
|
const endIndex = startIndex + ITEMS_PER_PAGE
|
||||||
const nextItems = suitableSearchResults.suitable.slice(startIndex, endIndex)
|
const nextItems = suitableSearchResults.slice(startIndex, endIndex)
|
||||||
|
|
||||||
if (nextItems.length > 0) {
|
if (nextItems.length > 0) {
|
||||||
setIsLoadingMore(true)
|
setIsLoadingMore(true)
|
||||||
@ -108,7 +107,7 @@ export default function SuitableList() {
|
|||||||
|
|
||||||
// 메모이제이션된 아이템 렌더링
|
// 메모이제이션된 아이템 렌더링
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(item: SuitableMain) => {
|
(item: Suitable) => {
|
||||||
const isSelected = isItemSelected(item.id)
|
const isSelected = isItemSelected(item.id)
|
||||||
const isOpen = openItems.has(item.id)
|
const isOpen = openItems.has(item.id)
|
||||||
|
|
||||||
@ -124,7 +123,7 @@ export default function SuitableList() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul className="reference-list check">
|
<ul className="reference-list check">
|
||||||
{toSuitableDetail(item.id).map((subItem: SuitableDetail) => (
|
{toSuitableDetail(item.detail).map((subItem: SuitableDetail) => (
|
||||||
<li className="reference-item" key={subItem.id}>
|
<li className="reference-item" key={subItem.id}>
|
||||||
<div className="check-item-wrap">
|
<div className="check-item-wrap">
|
||||||
<div className="check-form-box light">
|
<div className="check-form-box light">
|
||||||
@ -158,7 +157,7 @@ export default function SuitableList() {
|
|||||||
return <div>Loading...</div>
|
return <div>Loading...</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!suitableSearchResults?.suitable.length) {
|
if (!suitableSearchResults?.length) {
|
||||||
return <SuitableNoData />
|
return <SuitableNoData />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,173 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import Image from 'next/image'
|
|
||||||
import { useState, useEffect, useRef, useCallback, useMemo } from 'react'
|
|
||||||
import SuitableButton from './SuitableButton'
|
|
||||||
import SuitableNoData from './SuitableNoData'
|
|
||||||
import { useSuitableRaw, type Suitable } from '@/hooks/useSuitableRaw'
|
|
||||||
import { useSuitableStore } from '@/store/useSuitableStore'
|
|
||||||
import { SUITABLE_HEAD_CODE, type SuitableDetail } from '@/types/Suitable'
|
|
||||||
|
|
||||||
// 한 번에 로드할 아이템 수
|
|
||||||
const ITEMS_PER_PAGE = 100
|
|
||||||
|
|
||||||
export default function SuitableListRaw() {
|
|
||||||
const { toCodeName, suitableSearchResults, isSearchLoading, toSuitableDetail } = useSuitableRaw()
|
|
||||||
const { selectedItems, addSelectedItem, removeSelectedItem } = useSuitableStore()
|
|
||||||
const [openItems, setOpenItems] = useState<Set<number>>(new Set())
|
|
||||||
const [visibleItems, setVisibleItems] = useState<Suitable[]>([])
|
|
||||||
const [page, setPage] = useState(1)
|
|
||||||
const [isLoadingMore, setIsLoadingMore] = useState(false)
|
|
||||||
const observerTarget = useRef<HTMLDivElement>(null)
|
|
||||||
|
|
||||||
// 선택된 아이템 확인 함수 메모이제이션
|
|
||||||
const isItemSelected = useCallback(
|
|
||||||
(itemId: number) => {
|
|
||||||
return selectedItems.some((selected) => selected === itemId)
|
|
||||||
},
|
|
||||||
[selectedItems],
|
|
||||||
)
|
|
||||||
|
|
||||||
// 초기 데이터 로드
|
|
||||||
useEffect(() => {
|
|
||||||
if (suitableSearchResults) {
|
|
||||||
const initialItems = suitableSearchResults.slice(0, ITEMS_PER_PAGE)
|
|
||||||
setVisibleItems(initialItems)
|
|
||||||
setPage(1)
|
|
||||||
}
|
|
||||||
}, [suitableSearchResults])
|
|
||||||
|
|
||||||
// Intersection Observer 설정
|
|
||||||
useEffect(() => {
|
|
||||||
const observer = new IntersectionObserver(
|
|
||||||
(entries) => {
|
|
||||||
if (entries[0].isIntersecting && suitableSearchResults && !isLoadingMore) {
|
|
||||||
const nextPage = page + 1
|
|
||||||
const startIndex = (nextPage - 1) * ITEMS_PER_PAGE
|
|
||||||
const endIndex = startIndex + ITEMS_PER_PAGE
|
|
||||||
const nextItems = suitableSearchResults.slice(startIndex, endIndex)
|
|
||||||
|
|
||||||
if (nextItems.length > 0) {
|
|
||||||
setIsLoadingMore(true)
|
|
||||||
setVisibleItems((prev) => [...prev, ...nextItems])
|
|
||||||
setPage(nextPage)
|
|
||||||
setIsLoadingMore(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
threshold: 0.2,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
if (observerTarget.current) {
|
|
||||||
observer.observe(observerTarget.current)
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => observer.disconnect()
|
|
||||||
}, [page, suitableSearchResults, isLoadingMore])
|
|
||||||
|
|
||||||
const handleItemClick = useCallback(
|
|
||||||
(itemId: number) => {
|
|
||||||
isItemSelected(itemId) ? removeSelectedItem(itemId) : addSelectedItem(itemId)
|
|
||||||
},
|
|
||||||
[isItemSelected, addSelectedItem, removeSelectedItem],
|
|
||||||
)
|
|
||||||
|
|
||||||
const toggleItemOpen = useCallback((itemId: number) => {
|
|
||||||
setOpenItems((prev) => {
|
|
||||||
const newOpenItems = new Set(prev)
|
|
||||||
newOpenItems.has(itemId) ? newOpenItems.delete(itemId) : newOpenItems.add(itemId)
|
|
||||||
return newOpenItems
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// TODO: 추후 지붕재 적합성 데이터 CUD 구현 시 ×, ー 데이터 관리 필요
|
|
||||||
const suitableCheck = useCallback((value: string) => {
|
|
||||||
if (value === '×') {
|
|
||||||
return (
|
|
||||||
<div className="compliance-icon">
|
|
||||||
<Image src={'/assets/images/sub/compliance_x_icon.svg'} width={22} height={22} alt=""></Image>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
} else if (value === 'ー') {
|
|
||||||
return (
|
|
||||||
<div className="compliance-icon">
|
|
||||||
<Image src={'/assets/images/sub/compliance_quest_icon.svg'} width={22} height={22} alt=""></Image>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<div className="compliance-icon">
|
|
||||||
<Image src={'/assets/images/sub/compliance_check_icon.svg'} width={22} height={22} alt=""></Image>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// 메모이제이션된 아이템 렌더링
|
|
||||||
const renderItem = useCallback(
|
|
||||||
(item: Suitable) => {
|
|
||||||
const isSelected = isItemSelected(item.id)
|
|
||||||
const isOpen = openItems.has(item.id)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`compliance-check-bx ${isOpen ? 'act' : ''}`} key={item.id}>
|
|
||||||
<div className="check-name-wrap">
|
|
||||||
<div className="check-form-box ">
|
|
||||||
<input type="checkbox" id={`ch${item.id}`} checked={isSelected} onChange={() => handleItemClick(item.id)} />
|
|
||||||
<label htmlFor={`ch${item.id}`}>{item.productName}</label>
|
|
||||||
</div>
|
|
||||||
<div className="check-name-btn">
|
|
||||||
<button className="bx-btn" onClick={() => toggleItemOpen(item.id)}></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul className="reference-list check">
|
|
||||||
{toSuitableDetail(item.detail).map((subItem: SuitableDetail) => (
|
|
||||||
<li className="reference-item" key={subItem.id}>
|
|
||||||
<div className="check-item-wrap">
|
|
||||||
<div className="check-form-box light">
|
|
||||||
<input type="checkbox" id={`ch${subItem.id}`} />
|
|
||||||
<label htmlFor={`ch${subItem.id}`}>{toCodeName(SUITABLE_HEAD_CODE.TRESTLE_MFPC_CD, subItem.trestleMfpcCd)}</label>
|
|
||||||
</div>
|
|
||||||
<div className="compliance-icon-wrap">
|
|
||||||
{suitableCheck(subItem.trestleManufacturerProductName)}
|
|
||||||
{subItem.memo && (
|
|
||||||
<div className="compliance-icon">
|
|
||||||
<Image src={'/assets/images/sub/compliance_tip_icon.svg'} width={22} height={22} alt=""></Image>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
[isItemSelected, openItems, handleItemClick, toggleItemOpen, suitableCheck, toCodeName, toSuitableDetail],
|
|
||||||
)
|
|
||||||
|
|
||||||
// 메모이제이션된 아이템 리스트
|
|
||||||
const renderedItems = useMemo(() => {
|
|
||||||
return visibleItems.map(renderItem)
|
|
||||||
}, [visibleItems, renderItem])
|
|
||||||
|
|
||||||
if (isSearchLoading) {
|
|
||||||
return <div>Loading...</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!suitableSearchResults?.length) {
|
|
||||||
return <SuitableNoData />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{renderedItems}
|
|
||||||
<div ref={observerTarget} className="loading-indicator">
|
|
||||||
{isLoadingMore && <div className="loading-more">데이터를 불러오는 중...</div>}
|
|
||||||
</div>
|
|
||||||
<SuitableButton />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,118 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import Image from 'next/image'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import SuitableListRaw from './SuitableListRaw'
|
|
||||||
import { useSuitableRaw } from '@/hooks/useSuitableRaw'
|
|
||||||
import { useSuitableStore } from '@/store/useSuitableStore'
|
|
||||||
import type { CommCode } from '@/types/CommCode'
|
|
||||||
import { SUITABLE_HEAD_CODE } from '@/types/Suitable'
|
|
||||||
|
|
||||||
export default function SuitableRaw() {
|
|
||||||
const [reference, setReference] = useState(true)
|
|
||||||
|
|
||||||
const { getSuitableCommCode, refetchBySearch } = useSuitableRaw()
|
|
||||||
const { suitableCommCode, selectedCategory, setSelectedCategory, searchValue, setSearchValue, setIsSearch, clearSelectedItems } = useSuitableStore()
|
|
||||||
|
|
||||||
const handleInputSearch = async () => {
|
|
||||||
if (!searchValue.trim()) {
|
|
||||||
alert('屋根材の製品名を入力してください。')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setIsSearch(true)
|
|
||||||
refetchBySearch()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleInputClear = () => {
|
|
||||||
setSearchValue('')
|
|
||||||
setIsSearch(false)
|
|
||||||
refetchBySearch()
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
refetchBySearch()
|
|
||||||
}, [selectedCategory])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getSuitableCommCode()
|
|
||||||
return () => {
|
|
||||||
setSelectedCategory('')
|
|
||||||
setSearchValue('')
|
|
||||||
clearSelectedItems()
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="border-frame">
|
|
||||||
<span>테스트1 페이지</span>
|
|
||||||
<div className="sale-form-bx">
|
|
||||||
<select className="select-form" name="" id="" value={selectedCategory || ''} onChange={(e) => setSelectedCategory(e.target.value)}>
|
|
||||||
<option value="">屋根材を選択してください.</option>
|
|
||||||
{suitableCommCode.get(SUITABLE_HEAD_CODE.ROOF_MT_CD)?.map((category: CommCode, index: number) => (
|
|
||||||
<option key={index} value={category.code}>
|
|
||||||
{category.codeJp}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="sale-form-bx">
|
|
||||||
<div className="search-input">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="search-frame"
|
|
||||||
placeholder="屋根材 製品名を入力してください."
|
|
||||||
value={searchValue}
|
|
||||||
onChange={(e) => setSearchValue(e.target.value)}
|
|
||||||
/>
|
|
||||||
{searchValue && <button className="del-icon" onClick={handleInputClear} />}
|
|
||||||
<button className="search-icon" onClick={handleInputSearch} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="compliance-check-wrap">
|
|
||||||
<div className={`compliance-check-bx ${reference ? 'act' : ''}`}>
|
|
||||||
<div className="check-name-wrap">
|
|
||||||
<div className="check-name">凡例</div>
|
|
||||||
<div className="check-name-btn">
|
|
||||||
<button className="bx-btn" onClick={() => setReference(!reference)}></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul className="reference-list">
|
|
||||||
<li className="reference-item">
|
|
||||||
<div className="reference-item-bx">
|
|
||||||
<div className="compliance-icon">
|
|
||||||
<Image src={'/assets/images/sub/compliance_check_icon.svg'} width={22} height={22} alt=""></Image>
|
|
||||||
</div>
|
|
||||||
<span>設置可能</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li className="reference-item">
|
|
||||||
<div className="reference-item-bx">
|
|
||||||
<div className="compliance-icon">
|
|
||||||
<Image src={'/assets/images/sub/compliance_x_icon.svg'} width={22} height={22} alt=""></Image>
|
|
||||||
</div>
|
|
||||||
<span>設置不可</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li className="reference-item">
|
|
||||||
<div className="reference-item-bx">
|
|
||||||
<div className="compliance-icon">
|
|
||||||
<Image src={'/assets/images/sub/compliance_quest_icon.svg'} width={22} height={22} alt=""></Image>
|
|
||||||
</div>
|
|
||||||
<span>お問い合わせ</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li className="reference-item">
|
|
||||||
<div className="reference-item-bx">
|
|
||||||
<div className="compliance-icon">
|
|
||||||
<Image src={'/assets/images/sub/compliance_tip_icon.svg'} width={22} height={22} alt=""></Image>
|
|
||||||
</div>
|
|
||||||
<span>備考</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<SuitableListRaw />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -2,18 +2,45 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useSurveySaleTabState } from '@/store/surveySaleTabState'
|
import { useSurveySaleTabState } from '@/store/surveySaleTabState'
|
||||||
import { SurveyBasicRequest } from '@/types/Survey'
|
import type { SurveyBasicRequest } from '@/types/Survey'
|
||||||
import { Mode } from 'fs'
|
import type { Mode } from 'fs'
|
||||||
|
import { useSessionStore } from '@/store/session'
|
||||||
|
import { usePopupController } from '@/store/popupController'
|
||||||
|
import { useAddressStore } from '@/store/addressStore'
|
||||||
|
|
||||||
export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBasicInfo: (basicInfo: SurveyBasicRequest) => void; mode: Mode }) {
|
export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBasicInfo: (basicInfo: SurveyBasicRequest) => void; mode: Mode }) {
|
||||||
const { basicInfo, setBasicInfo, mode } = props
|
const { basicInfo, setBasicInfo, mode } = props
|
||||||
const { setBasicInfoSelected } = useSurveySaleTabState()
|
const { setBasicInfoSelected } = useSurveySaleTabState()
|
||||||
const [isFlip, setIsFlip] = useState<boolean>(true)
|
const [isFlip, setIsFlip] = useState<boolean>(true)
|
||||||
|
|
||||||
|
const { session } = useSessionStore()
|
||||||
|
const { addressData } = useAddressStore()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setBasicInfoSelected()
|
setBasicInfoSelected()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (session?.isLoggedIn) {
|
||||||
|
setBasicInfo({
|
||||||
|
...basicInfo,
|
||||||
|
representative: session.userNm ?? '',
|
||||||
|
store: session.storeNm ?? null,
|
||||||
|
constructionPoint: session.builderNo ?? null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (addressData) {
|
||||||
|
setBasicInfo({
|
||||||
|
...basicInfo,
|
||||||
|
postCode: addressData.post_code,
|
||||||
|
address: addressData.address,
|
||||||
|
addressDetail: addressData.address_detail,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [session, addressData])
|
||||||
|
|
||||||
|
const popupController = usePopupController()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={`sale-detail-toggle-bx ${isFlip ? 'act' : ''}`}>
|
<div className={`sale-detail-toggle-bx ${isFlip ? 'act' : ''}`}>
|
||||||
@ -31,31 +58,35 @@ export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBas
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="input-frame"
|
className="input-frame"
|
||||||
readOnly={mode === 'READ'}
|
readOnly
|
||||||
value={basicInfo?.representative ?? ''}
|
value={basicInfo?.representative ?? ''}
|
||||||
onChange={(e) => setBasicInfo({ ...basicInfo, representative: e.target.value })}
|
onChange={(e) => setBasicInfo({ ...basicInfo, representative: e.target.value })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
{(session?.role === 'Builder' || session?.role?.includes('Admin')) && (
|
||||||
<div className="data-input-form-tit">販売店</div>
|
<div className="data-input-form-bx">
|
||||||
<input
|
<div className="data-input-form-tit">販売店</div>
|
||||||
type="text"
|
<input
|
||||||
className="input-frame"
|
type="text"
|
||||||
readOnly={mode === 'READ'}
|
className="input-frame"
|
||||||
value={basicInfo?.store ?? ''}
|
readOnly
|
||||||
onChange={(e) => setBasicInfo({ ...basicInfo, store: e.target.value })}
|
value={basicInfo?.store ?? ''}
|
||||||
/>
|
onChange={(e) => setBasicInfo({ ...basicInfo, store: e.target.value })}
|
||||||
</div>
|
/>
|
||||||
<div className="data-input-form-bx">
|
</div>
|
||||||
<div className="data-input-form-tit">施工店</div>
|
)}
|
||||||
<input
|
{(session?.role === 'Builder' || session?.role === 'Partner') && (
|
||||||
type="text"
|
<div className="data-input-form-bx">
|
||||||
className="input-frame"
|
<div className="data-input-form-tit">施工店</div>
|
||||||
readOnly={mode === 'READ'}
|
<input
|
||||||
value={basicInfo?.constructionPoint ?? ''}
|
type="text"
|
||||||
onChange={(e) => setBasicInfo({ ...basicInfo, constructionPoint: e.target.value })}
|
className="input-frame"
|
||||||
/>
|
readOnly
|
||||||
</div>
|
value={basicInfo?.constructionPoint ?? ''}
|
||||||
|
onChange={(e) => setBasicInfo({ ...basicInfo, constructionPoint: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sale-frame">
|
<div className="sale-frame">
|
||||||
@ -67,7 +98,13 @@ export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBas
|
|||||||
<button className="date-btn">
|
<button className="date-btn">
|
||||||
<i className="date-icon"></i>
|
<i className="date-icon"></i>
|
||||||
</button>
|
</button>
|
||||||
<input type="date" className="date-frame" readOnly defaultValue={basicInfo?.investigationDate?.toString()} />
|
<input
|
||||||
|
id="investigationDate"
|
||||||
|
type="date"
|
||||||
|
className="date-frame"
|
||||||
|
defaultValue={basicInfo?.investigationDate?.toString()}
|
||||||
|
onChange={(e) => setBasicInfo({ ...basicInfo, investigationDate: e.target.value })}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<input type="date" className="input-frame" disabled defaultValue={basicInfo?.investigationDate?.toString()} />
|
<input type="date" className="input-frame" disabled defaultValue={basicInfo?.investigationDate?.toString()} />
|
||||||
@ -76,12 +113,24 @@ export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBas
|
|||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 건물명 */}
|
{/* 건물명 */}
|
||||||
<div className="data-input-form-tit">建物名</div>
|
<div className="data-input-form-tit">建物名</div>
|
||||||
<input type="text" className="input-frame" readOnly={mode === 'READ'} defaultValue={basicInfo?.buildingName ?? ''} />
|
<input
|
||||||
|
type="text"
|
||||||
|
className="input-frame"
|
||||||
|
readOnly={mode === 'READ'}
|
||||||
|
defaultValue={basicInfo?.buildingName ?? ''}
|
||||||
|
onChange={(e) => setBasicInfo({ ...basicInfo, buildingName: e.target.value })}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 고객명 */}
|
{/* 고객명 */}
|
||||||
<div className="data-input-form-tit">建物名</div>
|
<div className="data-input-form-tit">お客様名</div>
|
||||||
<input type="text" className="input-frame" readOnly={mode === 'READ'} defaultValue={basicInfo?.customerName ?? ''} />
|
<input
|
||||||
|
type="text"
|
||||||
|
className="input-frame"
|
||||||
|
readOnly={mode === 'READ'}
|
||||||
|
defaultValue={basicInfo?.customerName ?? ''}
|
||||||
|
onChange={(e) => setBasicInfo({ ...basicInfo, customerName: e.target.value })}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
<div className="data-input-form-tit">郵便番号/都道府県</div>
|
<div className="data-input-form-tit">郵便番号/都道府県</div>
|
||||||
@ -92,12 +141,12 @@ export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBas
|
|||||||
</div>
|
</div>
|
||||||
{/* 도도부현 */}
|
{/* 도도부현 */}
|
||||||
<div className="form-bx">
|
<div className="form-bx">
|
||||||
<input type="text" className="input-frame" readOnly={mode === 'READ'} defaultValue={basicInfo?.address ?? ''} disabled />
|
<input type="text" className="input-frame" readOnly={mode === 'READ'} defaultValue={basicInfo?.address ?? ''} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* 주소 */}
|
{/* 주소 */}
|
||||||
<div className="form-btn">
|
<div className="form-btn">
|
||||||
<button className="btn-frame n-blue icon">
|
<button className="btn-frame n-blue icon" onClick={() => popupController.setZipCodePopup(true)}>
|
||||||
郵便番号<i className="btn-arr"></i>
|
郵便番号<i className="btn-arr"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -105,7 +154,7 @@ export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBas
|
|||||||
|
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
<div className="data-input-form-tit">市区町村名, 以後の住所</div>
|
<div className="data-input-form-tit">市区町村名, 以後の住所</div>
|
||||||
<input type="text" className="input-frame" defaultValue={'浜松 浜松町'} readOnly={mode === 'READ'} />
|
<input type="text" className="input-frame" defaultValue={basicInfo?.addressDetail ?? ''} readOnly={mode === 'READ'} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,81 +1,267 @@
|
|||||||
import { Mode, SurveyBasicRequest, SurveyDetailRequest } from '@/types/Survey'
|
'use client'
|
||||||
|
|
||||||
export default function ButtonForm(props: { mode: Mode; setMode: (mode: Mode) => void; data: { basic: SurveyBasicRequest; roof: SurveyDetailRequest } }) {
|
import type { Mode, SurveyBasicRequest, SurveyDetailInfo, SurveyDetailRequest } from '@/types/Survey'
|
||||||
|
import { useSessionStore } from '@/store/session'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useParams, useRouter, useSearchParams } from 'next/navigation'
|
||||||
|
import { requiredFields, useServey } from '@/hooks/useSurvey'
|
||||||
|
|
||||||
|
export default function ButtonForm(props: {
|
||||||
|
mode: Mode
|
||||||
|
setMode: (mode: Mode) => void
|
||||||
|
data: { basic: SurveyBasicRequest; roof: SurveyDetailRequest }
|
||||||
|
}) {
|
||||||
|
// 라우터
|
||||||
|
const router = useRouter()
|
||||||
const { mode, setMode } = props
|
const { mode, setMode } = props
|
||||||
|
const { session } = useSessionStore()
|
||||||
|
|
||||||
|
const searchParams = useSearchParams()
|
||||||
|
const idParam = searchParams.get('id')
|
||||||
|
|
||||||
|
const params = useParams()
|
||||||
|
const routeId = params.id
|
||||||
|
|
||||||
|
const [isSubmitProcess, setIsSubmitProcess] = useState(false)
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// 권한
|
||||||
|
|
||||||
|
// 제출권한 ㅇ
|
||||||
|
const [isSubmiter, setIsSubmiter] = useState(false)
|
||||||
|
// 작성자
|
||||||
|
const [isWriter, setIsWriter] = useState(false)
|
||||||
|
const isSubmit = props.data.basic.submissionStatus
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (session?.isLoggedIn) {
|
||||||
|
setIsSubmiter(session.storeNm === props.data.basic.store && session.builderNo === props.data.basic.constructionPoint)
|
||||||
|
setIsWriter(session.userNm === props.data.basic.representative)
|
||||||
|
}
|
||||||
|
}, [session, props.data])
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// 저장/임시저장/수정
|
||||||
|
|
||||||
|
const id = routeId ? Number(routeId) : Number(idParam)
|
||||||
|
const { deleteSurvey, submitSurvey, updateSurvey } = useServey(Number(id))
|
||||||
|
const { validateSurveyDetail, createSurvey } = useServey()
|
||||||
|
let saveData = {
|
||||||
|
...props.data.basic,
|
||||||
|
detailInfo: props.data.roof,
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSave = (isTemporary: boolean) => {
|
||||||
|
const emptyField = validateSurveyDetail(props.data.roof)
|
||||||
|
console.log('handleSave, emptyField:: ', emptyField)
|
||||||
|
if (isTemporary) {
|
||||||
|
tempSaveProcess()
|
||||||
|
} else {
|
||||||
|
saveProcess(emptyField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tempSaveProcess = async () => {
|
||||||
|
if (idParam) {
|
||||||
|
await updateSurvey(saveData)
|
||||||
|
router.push(`/survey-sale/detail?id=${idParam}&isTemporary=true`)
|
||||||
|
} else {
|
||||||
|
const id = await createSurvey(saveData)
|
||||||
|
router.push(`/survey-sale/detail?id=${id}&isTemporary=true`)
|
||||||
|
}
|
||||||
|
alert('一時保存されました。')
|
||||||
|
}
|
||||||
|
|
||||||
|
const focusInput = (field: keyof SurveyDetailInfo) => {
|
||||||
|
const input = document.getElementById(field)
|
||||||
|
if (input) {
|
||||||
|
input.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveProcess = async (emptyField: string) => {
|
||||||
|
if (emptyField.trim() === '') {
|
||||||
|
if (idParam) {
|
||||||
|
// 수정 페이지에서 작성 후 제출
|
||||||
|
if (isSubmitProcess) {
|
||||||
|
saveData = {
|
||||||
|
...saveData,
|
||||||
|
submissionStatus: true,
|
||||||
|
submissionDate: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await updateSurvey(saveData)
|
||||||
|
router.push(`/survey-sale/${idParam}`)
|
||||||
|
} else {
|
||||||
|
const id = await createSurvey(saveData)
|
||||||
|
if (isSubmitProcess) {
|
||||||
|
submitProcess(id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
router.push(`/survey-sale/${id}`)
|
||||||
|
}
|
||||||
|
alert('保存されました。')
|
||||||
|
} else {
|
||||||
|
if (emptyField.includes('Unit')) {
|
||||||
|
alert('電気契約容量の単位を入力してください。')
|
||||||
|
focusInput(emptyField as keyof SurveyDetailInfo)
|
||||||
|
} else {
|
||||||
|
alert(requiredFields.find((field) => field.field === emptyField)?.name + ' 項目が空です。')
|
||||||
|
focusInput(emptyField as keyof SurveyDetailInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// 삭제/제출
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
if (routeId) {
|
||||||
|
window.neoConfirm('削除しますか?', async () => {
|
||||||
|
await deleteSurvey()
|
||||||
|
router.push('/survey-sale')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
window.neoConfirm('提出しますか?', async () => {
|
||||||
|
setIsSubmitProcess(true)
|
||||||
|
if (routeId) {
|
||||||
|
submitProcess()
|
||||||
|
} else {
|
||||||
|
handleSave(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const submitProcess = async (saveId?: number) => {
|
||||||
|
await submitSurvey(saveId)
|
||||||
|
alert('提出されました。')
|
||||||
|
router.push('/survey-sale')
|
||||||
|
}
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
if (mode === 'READ' && isSubmit && isSubmiter) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="sale-form-btn-wrap">
|
||||||
|
<div className="btn-flex-wrap">
|
||||||
|
<ListButton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{mode === 'CREATE' && (
|
{mode === 'READ' && (
|
||||||
<div className="sale-form-btn-wrap">
|
<div className="sale-form-btn-wrap">
|
||||||
<div className="btn-flex-wrap">
|
<div className="btn-flex-wrap">
|
||||||
<div className="btn-bx">
|
<ListButton />
|
||||||
{/* 임시저장 */}
|
<EditButton setMode={setMode} id={id.toString()} mode={mode} />
|
||||||
<button className="btn-frame n-blue icon" onClick={() => setMode('TEMP')}>
|
{(isWriter || !isSubmiter) && <DeleteButton handleDelete={handleDelete} />}
|
||||||
一時保存<i className="btn-arr"></i>
|
{!isSubmit && isSubmiter && <SubmitButton handleSubmit={handleSubmit} />}
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="btn-bx">
|
|
||||||
{/* 저장 */}
|
|
||||||
<button className="btn-frame red icon">
|
|
||||||
保存<i className="btn-arr"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="btn-bx">
|
|
||||||
{/* 목록 */}
|
|
||||||
<button className="btn-frame n-blue icon">
|
|
||||||
リスト<i className="btn-arr"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{mode === 'TEMP' && (
|
|
||||||
|
{(mode === 'CREATE' || mode === 'EDIT') && (
|
||||||
<div className="sale-form-btn-wrap">
|
<div className="sale-form-btn-wrap">
|
||||||
<div className="btn-flex-wrap">
|
<div className="btn-flex-wrap">
|
||||||
<div className="btn-bx">
|
<ListButton />
|
||||||
{/* 수정 */}
|
<TempButton setMode={setMode} handleSave={handleSave} />
|
||||||
<button className="btn-frame n-blue icon" onClick={() => setMode('EDIT')}>
|
<SaveButton handleSave={handleSave} />
|
||||||
修正<i className="btn-arr"></i>
|
<SubmitButton handleSubmit={handleSubmit} />
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="btn-bx">
|
|
||||||
{/* 삭제 */}
|
|
||||||
<button className="btn-frame n-blue icon">
|
|
||||||
削除<i className="btn-arr"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{mode === 'EDIT' && (
|
|
||||||
<div className="sale-form-btn-wrap">
|
|
||||||
<div className="btn-flex-wrap">
|
|
||||||
<div className="btn-bx">
|
|
||||||
{/* 목록 */}
|
|
||||||
<button className="btn-frame n-blue icon">
|
|
||||||
リスト<i className="btn-arr"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="btn-bx">
|
|
||||||
{/* 제출 */}
|
|
||||||
<button className="btn-frame red icon">
|
|
||||||
提出<i className="btn-arr"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="btn-bx">
|
|
||||||
{/* 수정 */}
|
|
||||||
<button className="btn-frame n-blue icon" onClick={() => setMode('EDIT')}>
|
|
||||||
修正<i className="btn-arr"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="btn-bx">
|
|
||||||
{/* 삭제 */}
|
|
||||||
<button className="btn-frame n-blue icon">
|
|
||||||
削除<i className="btn-arr"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 목록 버튼
|
||||||
|
function ListButton() {
|
||||||
|
const router = useRouter()
|
||||||
|
return (
|
||||||
|
<div className="btn-bx">
|
||||||
|
{/* 목록 */}
|
||||||
|
<button className="btn-frame n-blue icon" onClick={() => router.push('/survey-sale')}>
|
||||||
|
リスト<i className="btn-arr"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditButton(props: { setMode: (mode: Mode) => void; id: string; mode: Mode }) {
|
||||||
|
const { setMode, id, mode } = props
|
||||||
|
const router = useRouter()
|
||||||
|
return (
|
||||||
|
<div className="btn-bx">
|
||||||
|
{/* 수정 */}
|
||||||
|
<button
|
||||||
|
className="btn-frame n-blue icon"
|
||||||
|
onClick={() => {
|
||||||
|
router.push(`/survey-sale/regist?id=${id}`)
|
||||||
|
setMode('EDIT')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
修正<i className="btn-arr"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SubmitButton(props: { handleSubmit: () => void }) {
|
||||||
|
const { handleSubmit } = props
|
||||||
|
return (
|
||||||
|
<div className="btn-bx">
|
||||||
|
{/* 제출 */}
|
||||||
|
<button className="btn-frame red icon" onClick={handleSubmit}>
|
||||||
|
提出<i className="btn-arr"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DeleteButton(props: { handleDelete: () => void }) {
|
||||||
|
const { handleDelete } = props
|
||||||
|
return (
|
||||||
|
<div className="btn-bx">
|
||||||
|
{/* 삭제 */}
|
||||||
|
<button className="btn-frame n-blue icon" onClick={handleDelete}>
|
||||||
|
削除<i className="btn-arr"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SaveButton(props: { handleSave: (isTemporary: boolean) => void }) {
|
||||||
|
const { handleSave } = props
|
||||||
|
return (
|
||||||
|
<div className="btn-bx">
|
||||||
|
{/* 저장 */}
|
||||||
|
<button className="btn-frame n-blue icon" onClick={() => handleSave(false)}>
|
||||||
|
保存<i className="btn-arr"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TempButton(props: { setMode: (mode: Mode) => void; handleSave: (isTemporary: boolean) => void }) {
|
||||||
|
const { setMode, handleSave } = props
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="btn-bx">
|
||||||
|
{/* 임시저장 */}
|
||||||
|
<button
|
||||||
|
className="btn-frame n-blue icon"
|
||||||
|
onClick={() => {
|
||||||
|
setMode('TEMP')
|
||||||
|
handleSave(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
一時保存<i className="btn-arr"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { useServey } from '@/hooks/useSurvey'
|
|||||||
import { useParams, useSearchParams } from 'next/navigation'
|
import { useParams, useSearchParams } from 'next/navigation'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import DetailForm from './DetailForm'
|
import DetailForm from './DetailForm'
|
||||||
import { SurveyBasicInfo } from '@/types/Survey'
|
import type { SurveyBasicInfo } from '@/types/Survey'
|
||||||
|
|
||||||
export default function DataTable() {
|
export default function DataTable() {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
@ -83,7 +83,7 @@ export default function DataTable() {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<DetailForm surveyInfo={surveyDetail as SurveyBasicInfo} mode="READ" />
|
<DetailForm />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { Mode, SurveyBasicInfo, SurveyBasicRequest, SurveyDetailRequest } from '@/types/Survey'
|
import type { Mode, SurveyBasicInfo, SurveyBasicRequest, SurveyDetailRequest } from '@/types/Survey'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import ButtonForm from './ButtonForm'
|
import ButtonForm from './ButtonForm'
|
||||||
import BasicForm from './BasicForm'
|
import BasicForm from './BasicForm'
|
||||||
import RoofForm from './RoofForm'
|
import RoofForm from './RoofForm'
|
||||||
|
import { useParams, useSearchParams } from 'next/navigation'
|
||||||
|
import { useServey } from '@/hooks/useSurvey'
|
||||||
|
|
||||||
const roofInfoForm: SurveyDetailRequest = {
|
const roofInfoForm: SurveyDetailRequest = {
|
||||||
contractCapacity: null,
|
contractCapacity: null,
|
||||||
retailCompany: null,
|
retailCompany: null,
|
||||||
@ -57,21 +60,32 @@ const basicInfoForm: SurveyBasicRequest = {
|
|||||||
submissionDate: null,
|
submissionDate: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DetailForm(props: { surveyInfo?: SurveyBasicInfo; mode?: Mode }) {
|
export default function DetailForm() {
|
||||||
const [mode, setMode] = useState<Mode>(props.mode ?? 'CREATE')
|
const idParam = useSearchParams().get('id')
|
||||||
|
const routeId = useParams().id
|
||||||
|
|
||||||
|
const id = idParam ?? routeId
|
||||||
|
|
||||||
|
const { surveyDetail } = useServey(Number(id))
|
||||||
|
|
||||||
|
const [mode, setMode] = useState<Mode>(idParam ? 'EDIT' : routeId ? 'READ' : 'CREATE')
|
||||||
const [basicInfoData, setBasicInfoData] = useState<SurveyBasicRequest>(basicInfoForm)
|
const [basicInfoData, setBasicInfoData] = useState<SurveyBasicRequest>(basicInfoForm)
|
||||||
const [roofInfoData, setRoofInfoData] = useState<SurveyDetailRequest>(roofInfoForm)
|
const [roofInfoData, setRoofInfoData] = useState<SurveyDetailRequest>(roofInfoForm)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.surveyInfo && (mode === 'EDIT' || mode === 'READ')) {
|
if (surveyDetail && (mode === 'EDIT' || mode === 'READ')) {
|
||||||
const { id, uptDt, regDt, detailInfo, ...rest } = props.surveyInfo
|
const { id, uptDt, regDt, detailInfo, ...rest } = surveyDetail
|
||||||
setBasicInfoData(rest)
|
setBasicInfoData(rest)
|
||||||
if (detailInfo) {
|
if (detailInfo) {
|
||||||
const { id, uptDt, regDt, basicInfoId, ...rest } = detailInfo
|
const { id, uptDt, regDt, basicInfoId, ...rest } = detailInfo
|
||||||
setRoofInfoData(rest)
|
setRoofInfoData(rest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [props.surveyInfo, mode])
|
}, [surveyDetail, mode])
|
||||||
|
|
||||||
|
// console.log('mode:: ', mode)
|
||||||
|
// console.log('surveyDetail:: ', surveyDetail)
|
||||||
|
// console.log('roofInfoData:: ', roofInfoData)
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
basic: basicInfoData,
|
basic: basicInfoData,
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
import { Mode } from '@/types/Survey'
|
|
||||||
import { useSearchParams } from 'next/navigation'
|
|
||||||
import DetailForm from './DetailForm'
|
|
||||||
import { useServey } from '@/hooks/useSurvey'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { SurveyBasicInfo } from '@/types/Survey'
|
|
||||||
import { useSessionStore } from '@/store/session'
|
|
||||||
|
|
||||||
export default function RegistForm() {
|
|
||||||
const searchParams = useSearchParams()
|
|
||||||
const id = searchParams.get('id')
|
|
||||||
|
|
||||||
const { surveyDetail } = useServey(Number(id))
|
|
||||||
const { session } = useSessionStore()
|
|
||||||
|
|
||||||
const [mode, setMode] = useState<Mode>('CREATE')
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (id) {
|
|
||||||
setMode('EDIT')
|
|
||||||
}
|
|
||||||
}, [id])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<DetailForm mode={mode} surveyInfo={surveyDetail as SurveyBasicInfo} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Mode, SurveyDetailInfo, SurveyDetailRequest } from '@/types/Survey'
|
import type { Mode, SurveyDetailInfo, SurveyDetailRequest } from '@/types/Survey'
|
||||||
|
|
||||||
type RadioEtcKeys = 'houseStructure' | 'rafterMaterial' | 'waterproofMaterial' | 'insulationPresence' | 'rafterDirection' | 'leakTrace'
|
type RadioEtcKeys = 'houseStructure' | 'rafterMaterial' | 'waterproofMaterial' | 'insulationPresence' | 'rafterDirection' | 'leakTrace'
|
||||||
type SelectBoxKeys =
|
type SelectBoxKeys =
|
||||||
@ -12,14 +12,14 @@ type SelectBoxKeys =
|
|||||||
| 'structureOrder'
|
| 'structureOrder'
|
||||||
| 'installationAvailability'
|
| 'installationAvailability'
|
||||||
|
|
||||||
export const supplementary_facilities = [
|
export const supplementaryFacilities = [
|
||||||
{ id: 1, name: 'エコキュート' }, //에코큐트
|
{ id: 1, name: 'エコキュート' }, //에코큐트
|
||||||
{ id: 2, name: 'エネパーム' }, //에네팜
|
{ id: 2, name: 'エネパーム' }, //에네팜
|
||||||
{ id: 3, name: '蓄電池システム' }, //축전지시스템
|
{ id: 3, name: '蓄電池システム' }, //축전지시스템
|
||||||
{ id: 4, name: '太陽光発電' }, //태양광발전
|
{ id: 4, name: '太陽光発電' }, //태양광발전
|
||||||
]
|
]
|
||||||
|
|
||||||
export const roof_material = [
|
export const roofMaterial = [
|
||||||
{ id: 1, name: 'スレート' }, //슬레이트
|
{ id: 1, name: 'スレート' }, //슬레이트
|
||||||
{ id: 2, name: 'アスファルトシングル' }, //아스팔트 싱글
|
{ id: 2, name: 'アスファルトシングル' }, //아스팔트 싱글
|
||||||
{ id: 3, name: '瓦' }, //기와
|
{ id: 3, name: '瓦' }, //기와
|
||||||
@ -200,19 +200,47 @@ export const radioEtcData: Record<RadioEtcKeys, { id: number; label: string }[]>
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const makeNumArr = (value: string) => {
|
||||||
|
return value
|
||||||
|
.split(',')
|
||||||
|
.map((v) => v.trim())
|
||||||
|
.filter((v) => v.length > 0)
|
||||||
|
}
|
||||||
|
|
||||||
export default function RoofForm(props: {
|
export default function RoofForm(props: {
|
||||||
roofInfo: SurveyDetailRequest | SurveyDetailInfo
|
roofInfo: SurveyDetailRequest | SurveyDetailInfo
|
||||||
setRoofInfo: (roofInfo: SurveyDetailRequest) => void
|
setRoofInfo: (roofInfo: SurveyDetailRequest) => void
|
||||||
mode: Mode
|
mode: Mode
|
||||||
}) {
|
}) {
|
||||||
const makeNumArr = (value: string) => {
|
|
||||||
return value
|
|
||||||
.split(',')
|
|
||||||
.map((v) => v.trim())
|
|
||||||
.filter((v) => v.length > 0)
|
|
||||||
}
|
|
||||||
const { roofInfo, setRoofInfo, mode } = props
|
const { roofInfo, setRoofInfo, mode } = props
|
||||||
const [isFlip, setIsFlip] = useState<boolean>(true)
|
const [isFlip, setIsFlip] = useState<boolean>(true)
|
||||||
|
|
||||||
|
const handleNumberInput = (key: keyof SurveyDetailRequest, value: number | string) => {
|
||||||
|
if (key === 'roofSlope' || key === 'openFieldPlateThickness') {
|
||||||
|
const stringValue = value.toString()
|
||||||
|
if (stringValue.length > 5) {
|
||||||
|
alert('保存できるサイズを超えました。')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (stringValue.includes('.')) {
|
||||||
|
const decimalPlaces = stringValue.split('.')[1].length
|
||||||
|
if (decimalPlaces > 1) {
|
||||||
|
alert('小数点以下1桁までしか許されません。')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setRoofInfo({ ...roofInfo, [key]: value.toString() })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUnitInput = (value: string) => {
|
||||||
|
const numericValue = roofInfo.contractCapacity?.replace(/[^0-9.]/g, '') || ''
|
||||||
|
setRoofInfo({
|
||||||
|
...roofInfo,
|
||||||
|
contractCapacity: numericValue ? `${numericValue} ${value}` : value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`sale-detail-toggle-bx ${isFlip ? 'act' : ''}`}>
|
<div className={`sale-detail-toggle-bx ${isFlip ? 'act' : ''}`}>
|
||||||
<div className="sale-detail-toggle-head" onClick={() => setIsFlip(!isFlip)}>
|
<div className="sale-detail-toggle-head" onClick={() => setIsFlip(!isFlip)}>
|
||||||
@ -229,78 +257,55 @@ export default function RoofForm(props: {
|
|||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 전기 계약 용량 */}
|
{/* 전기 계약 용량 */}
|
||||||
<div className="data-input-form-tit">電気契約容量</div>
|
<div className="data-input-form-tit">電気契約容量</div>
|
||||||
<div className="data-input mb5">
|
{mode === 'READ' && <input type="text" className="input-frame" value={roofInfo?.contractCapacity ?? ''} disabled={mode === 'READ'} />}
|
||||||
<input type="text" className="input-frame" value={roofInfo?.contractCapacity ?? ''} readOnly={mode === 'READ'} />
|
|
||||||
</div>
|
|
||||||
{mode === 'READ' && <input type="text" className="input-frame" defaultValue={'10'} readOnly={mode === 'READ'} />}
|
|
||||||
{mode !== 'READ' && (
|
{mode !== 'READ' && (
|
||||||
<div className="data-input">
|
<div className="data-input mb5">
|
||||||
<select className="select-form" name="" id="">
|
<input
|
||||||
<option value="">kVA</option>
|
type="text"
|
||||||
<option value="">kVA</option>
|
id="contractCapacity"
|
||||||
<option value="">kVA</option>
|
className="input-frame"
|
||||||
<option value="">kVA</option>
|
value={roofInfo?.contractCapacity?.split(' ')[0] ?? ''}
|
||||||
<option value="">kVA</option>
|
onChange={(e) => handleNumberInput('contractCapacity', e.target.value)}
|
||||||
</select>
|
/>
|
||||||
|
<div className="data-input">
|
||||||
|
<select
|
||||||
|
className="select-form"
|
||||||
|
name="contractCapacityUnit"
|
||||||
|
id="contractCapacityUnit"
|
||||||
|
value={roofInfo?.contractCapacity?.split(' ')[1] ?? ''}
|
||||||
|
onChange={(e) => handleUnitInput(e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="" hidden>
|
||||||
|
단위
|
||||||
|
</option>
|
||||||
|
<option value="A">A</option>
|
||||||
|
<option value="kVA">kVA</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 전기 소매 회사사 */}
|
{/* 전기 소매 회사사 */}
|
||||||
<div className="data-input-form-tit">電気小売会社</div>
|
<div className="data-input-form-tit">電気小売会社</div>
|
||||||
<input type="text" className="input-frame" value={roofInfo?.retailCompany ?? ''} readOnly={mode === 'READ'} />
|
<input
|
||||||
|
type="text"
|
||||||
|
className="input-frame"
|
||||||
|
value={roofInfo?.retailCompany ?? ''}
|
||||||
|
disabled={mode === 'READ'}
|
||||||
|
onChange={(e) => setRoofInfo({ ...roofInfo, retailCompany: e.target.value })}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 전기 부대 설비 */}
|
{/* 전기 부대 설비 */}
|
||||||
<div className="data-input-form-tit">
|
<div className="data-input-form-tit">
|
||||||
電気袋設備<span>※複数選択可能</span>
|
電気袋設備<span>※複数選択可能</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="data-check-wrap">
|
<MultiCheck mode={mode} column="supplementaryFacilities" roofInfo={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
|
||||||
{/* <div className="check-form-box">
|
|
||||||
<input type="checkbox" id="ch01" readOnly={mode === 'READ'} />
|
|
||||||
<label htmlFor="ch01">エコキュート</label>
|
|
||||||
</div> */}
|
|
||||||
{supplementary_facilities.map((item) => (
|
|
||||||
<div className="check-form-box" key={item.id}>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id={`${item.id}`}
|
|
||||||
checked={makeNumArr(roofInfo?.supplementaryFacilities ?? '').includes(String(item.id))}
|
|
||||||
readOnly={mode === 'READ'}
|
|
||||||
/>
|
|
||||||
<label htmlFor={`${item.id}`}>{item.name}</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div className="check-form-box">
|
|
||||||
<input type="checkbox" id={`supplementaryFacilitiesEtc`} checked={roofInfo?.supplementaryFacilitiesEtc !== null} readOnly />
|
|
||||||
<label htmlFor={`supplementaryFacilitiesEtc`}>その他 (直接入力)</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="data-input">
|
|
||||||
<input type="text" className="input-frame" placeholder="-" readOnly value={roofInfo?.supplementaryFacilitiesEtc ?? ''} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 설치 희망 시스템 */}
|
<div className="data-input-form-tit red-f">設置希望システム</div>
|
||||||
{/* <div className="data-input-form-tit red-f">設置希望システム</div>
|
<SelectedBox mode={mode} column="installationSystem" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
|
||||||
{mode === 'READ' && (
|
|
||||||
<div className="data-input">
|
|
||||||
<input type="text" className="input-frame" defaultValue={''} disabled />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{mode !== 'READ' && (
|
|
||||||
<div className="data-input mb5">
|
|
||||||
<select className="select-form" name="" id="">
|
|
||||||
<option value="">太陽光発電</option>
|
|
||||||
<option value="">太陽光発電</option>
|
|
||||||
<option value="">太陽光発電</option>
|
|
||||||
<option value="">太陽光発電</option>
|
|
||||||
<option value="">太陽光発電</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
)} */}
|
|
||||||
<div className="data-input-form-tit">設置希望システム</div>
|
|
||||||
<SelectedBox column="installationSystem" detailInfoData={roofInfo as SurveyDetailInfo} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -312,108 +317,73 @@ export default function RoofForm(props: {
|
|||||||
{/* 건축 연수 */}
|
{/* 건축 연수 */}
|
||||||
<div className="data-input-form-tit red-f">建築研修</div>
|
<div className="data-input-form-tit red-f">建築研修</div>
|
||||||
<div className="data-input mb5">
|
<div className="data-input mb5">
|
||||||
{/* {mode === 'READ' && <input type="text" className="input-frame" defaultValue={''} disabled />}
|
<SelectedBox mode={mode} column="constructionYear" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
|
||||||
{mode !== 'READ' && (
|
|
||||||
<select className="select-form" name="" id="">
|
|
||||||
<option value="">新築</option>
|
|
||||||
<option value="">新築</option>
|
|
||||||
<option value="">新築</option>
|
|
||||||
<option value="">新築</option>
|
|
||||||
<option value="">新築</option>
|
|
||||||
</select>
|
|
||||||
)} */}
|
|
||||||
<SelectedBox column="constructionYear" detailInfoData={roofInfo as SurveyDetailInfo} />
|
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="data-input flex">
|
|
||||||
<input type="text" className="input-frame" defaultValue={''} disabled />
|
|
||||||
<span>年</span>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 지붕재 */}
|
{/* 지붕재 */}
|
||||||
<div className="data-input-form-tit">
|
<div className="data-input-form-tit">
|
||||||
屋根材<span>※最大2個まで選択可能</span>
|
屋根材<span>※最大2個まで選択可能</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="data-check-wrap">
|
<MultiCheck mode={mode} column="roofMaterial" roofInfo={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
|
||||||
{roof_material.map((item) => (
|
|
||||||
<div className="check-form-box" key={item.id}>
|
|
||||||
<input type="checkbox" id={`${item.id}`} checked={makeNumArr(roofInfo?.roofMaterial ?? '').includes(String(item.id))} readOnly />
|
|
||||||
<label htmlFor={`${item.id}`}>{item.name}</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div className="check-form-box">
|
|
||||||
<input type="checkbox" id={`roofMaterialEtc`} checked={roofInfo?.roofMaterialEtc !== null} readOnly />
|
|
||||||
<label htmlFor={`roofMaterialEtc`}>その他 (直接入力)</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="data-input">
|
|
||||||
<input type="text" className="input-frame" placeholder="-" readOnly value={roofInfo?.roofMaterialEtc ?? ''} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 지붕 모양 */}
|
{/* 지붕 모양 */}
|
||||||
<div className="data-input-form-tit">建築研修</div>
|
<div className="data-input-form-tit">屋根の形状</div>
|
||||||
<div className="data-input mb5">
|
<div className="data-input mb5">
|
||||||
{/* {mode === 'READ' && <input type="text" className="input-frame" defaultValue={''} disabled />}
|
<SelectedBox mode={mode} column="roofShape" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
|
||||||
{mode !== 'READ' && (
|
|
||||||
<select className="select-form" name="" id="">
|
|
||||||
<option value="">太陽光発電</option>
|
|
||||||
<option value="">太陽光発電</option>
|
|
||||||
<option value="">太陽光発電</option>
|
|
||||||
<option value="">太陽光発電</option>
|
|
||||||
<option value="">太陽光発電</option>
|
|
||||||
</select>
|
|
||||||
)} */}
|
|
||||||
<SelectedBox column="roofShape" detailInfoData={roofInfo as SurveyDetailInfo} />
|
|
||||||
</div>
|
|
||||||
<div className="data-input">
|
|
||||||
<input type="text" className="input-frame" defaultValue={''} disabled />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 지붕 경사도도 */}
|
{/* 지붕 경사도도 */}
|
||||||
<div className="data-input-form-tit">屋根の斜面</div>
|
<div className="data-input-form-tit">屋根の斜面</div>
|
||||||
<div className="data-input flex">
|
<div className="data-input flex">
|
||||||
<input type="text" className="input-frame" value={roofInfo?.roofSlope ?? ''} readOnly={mode === 'READ'} />
|
<input
|
||||||
|
type="number"
|
||||||
|
className="input-frame"
|
||||||
|
value={roofInfo?.roofSlope ?? ''}
|
||||||
|
disabled={mode === 'READ'}
|
||||||
|
onChange={(e) => handleNumberInput('roofSlope', e.target.value)}
|
||||||
|
/>
|
||||||
<span>寸</span>
|
<span>寸</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 주택구조조 */}
|
{/* 주택구조조 */}
|
||||||
<div className="data-input-form-tit">住宅構造</div>
|
<div className="data-input-form-tit">住宅構造</div>
|
||||||
<RadioSelected column="houseStructure" detailInfoData={roofInfo as SurveyDetailInfo} />
|
<RadioSelected mode={mode} column="houseStructure" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 서까래 재질 */}
|
{/* 서까래 재질 */}
|
||||||
<div className="data-input-form-tit">垂木材質</div>
|
<div className="data-input-form-tit">垂木材質</div>
|
||||||
<RadioSelected column="rafterMaterial" detailInfoData={roofInfo as SurveyDetailInfo} />
|
<RadioSelected mode={mode} column="rafterMaterial" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 서까래 크기 */}
|
{/* 서까래 크기 */}
|
||||||
<div className="data-input-form-tit red-f">垂木サイズ</div>
|
<div className="data-input-form-tit red-f">垂木サイズ</div>
|
||||||
<div className="data-input mb5">
|
<div className="data-input mb5">
|
||||||
<SelectedBox column="rafterSize" detailInfoData={roofInfo as SurveyDetailInfo} />
|
<SelectedBox mode={mode} column="rafterSize" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 서까래 피치 */}
|
{/* 서까래 피치 */}
|
||||||
<div className="data-input-form-tit red-f">垂木サイズ</div>
|
<div className="data-input-form-tit red-f">垂木サイズ</div>
|
||||||
<div className="data-input mb5">
|
<div className="data-input mb5">
|
||||||
<SelectedBox column="rafterPitch" detailInfoData={roofInfo as SurveyDetailInfo} />
|
<SelectedBox mode={mode} column="rafterPitch" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 서까래 방향 */}
|
{/* 서까래 방향 */}
|
||||||
<div className="data-input-form-tit red-f">垂木の方向</div>
|
<div className="data-input-form-tit red-f">垂木の方向</div>
|
||||||
<div className="data-check-wrap mb0">
|
<div className="data-check-wrap mb0">
|
||||||
<RadioSelected column="rafterDirection" detailInfoData={roofInfo as SurveyDetailInfo} />
|
<RadioSelected mode={mode} column="rafterDirection" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 노지판 종류류 */}
|
{/* 노지판 종류류 */}
|
||||||
<div className="data-input-form-tit">路地板の種類</div>
|
<div className="data-input-form-tit">路地板の種類</div>
|
||||||
<div className="data-input mb5">
|
<div className="data-input mb5">
|
||||||
<SelectedBox column="openFieldPlateKind" detailInfoData={roofInfo as SurveyDetailInfo} />
|
<SelectedBox mode={mode} column="openFieldPlateKind" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
@ -422,7 +392,13 @@ export default function RoofForm(props: {
|
|||||||
路地板厚<span>※小幅板を選択した場合, 厚さ. 小幅板間の間隔寸法を記載</span>
|
路地板厚<span>※小幅板を選択した場合, 厚さ. 小幅板間の間隔寸法を記載</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input flex">
|
<div className="data-input flex">
|
||||||
<input type="text" className="input-frame" value={roofInfo?.openFieldPlateThickness ?? ''} readOnly={mode === 'READ'} />
|
<input
|
||||||
|
type="text"
|
||||||
|
className="input-frame"
|
||||||
|
value={roofInfo?.openFieldPlateThickness ?? ''}
|
||||||
|
disabled={mode === 'READ'}
|
||||||
|
onChange={(e) => handleNumberInput('openFieldPlateThickness', e.target.value)}
|
||||||
|
/>
|
||||||
<span>mm</span>
|
<span>mm</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -430,28 +406,28 @@ export default function RoofForm(props: {
|
|||||||
{/* 누수 흔적 */}
|
{/* 누수 흔적 */}
|
||||||
<div className="data-input-form-tit ">水漏れの痕跡</div>
|
<div className="data-input-form-tit ">水漏れの痕跡</div>
|
||||||
<div className="data-check-wrap mb0">
|
<div className="data-check-wrap mb0">
|
||||||
<RadioSelected column="leakTrace" detailInfoData={roofInfo as SurveyDetailInfo} />
|
<RadioSelected mode={mode} column="leakTrace" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 방수재 종류 */}
|
{/* 방수재 종류 */}
|
||||||
<div className="data-input-form-tit red-f">防水材の種類</div>
|
<div className="data-input-form-tit red-f">防水材の種類</div>
|
||||||
<RadioSelected column="waterproofMaterial" detailInfoData={roofInfo as SurveyDetailInfo} />
|
<RadioSelected mode={mode} column="waterproofMaterial" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 단열재 유무 */}
|
{/* 단열재 유무 */}
|
||||||
<div className="data-input-form-tit red-f">断熱材の有無</div>
|
<div className="data-input-form-tit red-f">断熱材の有無</div>
|
||||||
<RadioSelected column="insulationPresence" detailInfoData={roofInfo as SurveyDetailInfo} />
|
<RadioSelected mode={mode} column="insulationPresence" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 지붕 구조의 순서 */}
|
{/* 지붕 구조의 순서 */}
|
||||||
<div className="data-input-form-tit red-f">屋根構造の順序</div>
|
<div className="data-input-form-tit red-f">屋根構造の順序</div>
|
||||||
<SelectedBox column="structureOrder" detailInfoData={roofInfo as SurveyDetailInfo} />
|
<SelectedBox mode={mode} column="structureOrder" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 지붕 제품명 설치 가능 여부 확인 */}
|
{/* 지붕 제품명 설치 가능 여부 확인 */}
|
||||||
<div className="data-input-form-tit">屋根製品名 設置可否確認</div>
|
<div className="data-input-form-tit">屋根製品名 設置可否確認</div>
|
||||||
<SelectedBox column="installationAvailability" detailInfoData={roofInfo as SurveyDetailInfo} />
|
<SelectedBox mode={mode} column="installationAvailability" detailInfoData={roofInfo as SurveyDetailInfo} setRoofInfo={setRoofInfo} />
|
||||||
</div>
|
</div>
|
||||||
<div className="data-input-form-bx">
|
<div className="data-input-form-bx">
|
||||||
{/* 메모 */}
|
{/* 메모 */}
|
||||||
@ -459,11 +435,12 @@ export default function RoofForm(props: {
|
|||||||
<div className="data-input">
|
<div className="data-input">
|
||||||
<textarea
|
<textarea
|
||||||
className="textarea-form"
|
className="textarea-form"
|
||||||
name=""
|
name="memo"
|
||||||
id=""
|
id="memo"
|
||||||
placeholder="TextArea Filed"
|
placeholder="TextArea Filed"
|
||||||
value={roofInfo?.memo ?? ''}
|
value={roofInfo?.memo ?? ''}
|
||||||
readOnly={mode === 'READ'}
|
disabled={mode === 'READ'}
|
||||||
|
onChange={(e) => setRoofInfo({ ...roofInfo, memo: e.target.value })}
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -474,23 +451,107 @@ export default function RoofForm(props: {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const SelectedBox = ({ column, detailInfoData }: { column: string; detailInfoData: SurveyDetailInfo }) => {
|
const SelectedBox = ({
|
||||||
|
mode,
|
||||||
|
column,
|
||||||
|
detailInfoData,
|
||||||
|
setRoofInfo,
|
||||||
|
}: {
|
||||||
|
mode: Mode
|
||||||
|
column: string
|
||||||
|
detailInfoData: SurveyDetailInfo
|
||||||
|
setRoofInfo: (roofInfo: SurveyDetailRequest) => void
|
||||||
|
}) => {
|
||||||
const selectedId = detailInfoData?.[column as keyof SurveyDetailInfo]
|
const selectedId = detailInfoData?.[column as keyof SurveyDetailInfo]
|
||||||
const etcValue = detailInfoData?.[`${column}Etc` as keyof SurveyDetailInfo]
|
const etcValue = detailInfoData?.[`${column}Etc` as keyof SurveyDetailInfo]
|
||||||
|
|
||||||
|
const [isEtcSelected, setIsEtcSelected] = useState<boolean>(etcValue !== null && etcValue !== undefined && etcValue !== '')
|
||||||
|
const [etcVal, setEtcVal] = useState<string>(etcValue?.toString() ?? '')
|
||||||
|
|
||||||
|
const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
const value = e.target.value
|
||||||
|
const isSpecialCase = column === 'constructionYear' || column === 'installationAvailability'
|
||||||
|
const isEtc = value === 'etc'
|
||||||
|
const isSpecialEtc = isSpecialCase && value === '2'
|
||||||
|
|
||||||
|
const updatedData: typeof detailInfoData = {
|
||||||
|
...detailInfoData,
|
||||||
|
[column]: isEtc ? null : value,
|
||||||
|
[`${column}Etc`]: isEtc ? '' : null,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSpecialEtc) {
|
||||||
|
updatedData[column] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsEtcSelected(isEtc || isSpecialEtc)
|
||||||
|
if (!isEtc) setEtcVal('')
|
||||||
|
setRoofInfo(updatedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEtcInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value
|
||||||
|
setEtcVal(value)
|
||||||
|
setRoofInfo({ ...detailInfoData, [`${column}Etc`]: value })
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<select className="select-form mb10" name={column} id={column} disabled value={selectedId ? 'selected' : etcValue ? 'etc' : ''}>
|
<select
|
||||||
<option value="">-</option>
|
className="select-form mb10"
|
||||||
<option value="etc">その他 (直接入力)</option>
|
name={column}
|
||||||
<option value="selected">{selectBoxOptions[column as keyof typeof selectBoxOptions][Number(selectedId)]?.name}</option>
|
id={column}
|
||||||
|
disabled={mode === 'READ'}
|
||||||
|
value={selectedId ? Number(selectedId) : etcValue !== null ? 'etc' : ''}
|
||||||
|
onChange={handleSelectChange}
|
||||||
|
>
|
||||||
|
{selectBoxOptions[column as keyof typeof selectBoxOptions].map((item) => (
|
||||||
|
<option key={item.id} value={item.id}>
|
||||||
|
{item.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
{column !== 'installationAvailability' && column !== 'constructionYear' && (
|
||||||
|
<option key="etc" value="etc">
|
||||||
|
その他 (直接入力)
|
||||||
|
</option>
|
||||||
|
)}
|
||||||
|
<option key="" value="" hidden>
|
||||||
|
選択してください
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
{etcValue && <input type="text" className="input-frame" readOnly value={etcValue.toString()} />}
|
<div className="data-input">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="input-frame"
|
||||||
|
placeholder="-"
|
||||||
|
value={etcVal}
|
||||||
|
onChange={handleEtcInputChange}
|
||||||
|
disabled={
|
||||||
|
mode === 'READ'
|
||||||
|
? true
|
||||||
|
: column === 'installationAvailability'
|
||||||
|
? false
|
||||||
|
: column === 'constructionYear'
|
||||||
|
? detailInfoData.constructionYear === '1' || detailInfoData.constructionYear === null
|
||||||
|
: !isEtcSelected
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const RadioSelected = ({ column, detailInfoData }: { column: string; detailInfoData: SurveyDetailInfo | null }) => {
|
const RadioSelected = ({
|
||||||
|
mode,
|
||||||
|
column,
|
||||||
|
detailInfoData,
|
||||||
|
setRoofInfo,
|
||||||
|
}: {
|
||||||
|
mode: Mode
|
||||||
|
column: string
|
||||||
|
detailInfoData: SurveyDetailInfo
|
||||||
|
setRoofInfo: (roofInfo: SurveyDetailRequest) => void
|
||||||
|
}) => {
|
||||||
let selectedId = detailInfoData?.[column as keyof SurveyDetailInfo]
|
let selectedId = detailInfoData?.[column as keyof SurveyDetailInfo]
|
||||||
if (column === 'leakTrace') {
|
if (column === 'leakTrace') {
|
||||||
selectedId = Number(selectedId)
|
selectedId = Number(selectedId)
|
||||||
@ -501,28 +562,182 @@ const RadioSelected = ({ column, detailInfoData }: { column: string; detailInfoD
|
|||||||
if (column !== 'rafterDirection') {
|
if (column !== 'rafterDirection') {
|
||||||
etcValue = detailInfoData?.[`${column}Etc` as keyof SurveyDetailInfo]
|
etcValue = detailInfoData?.[`${column}Etc` as keyof SurveyDetailInfo]
|
||||||
}
|
}
|
||||||
const etcChecked = etcValue !== null && etcValue !== undefined && etcValue !== ''
|
const [etcChecked, setEtcChecked] = useState<boolean>(etcValue !== null && etcValue !== undefined && etcValue !== '')
|
||||||
|
const [etcVal, setEtcVal] = useState<string>(etcValue?.toString() ?? '')
|
||||||
|
|
||||||
|
const handleRadioChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value
|
||||||
|
if (column === 'leakTrace') {
|
||||||
|
handleBooleanRadioChange(value)
|
||||||
|
}
|
||||||
|
if (value === 'etc') {
|
||||||
|
setEtcChecked(true)
|
||||||
|
setRoofInfo({ ...detailInfoData, [column]: null, [`${column}Etc`]: '' })
|
||||||
|
} else {
|
||||||
|
if (column === 'insulationPresence' && value === '2') {
|
||||||
|
setEtcChecked(true)
|
||||||
|
} else {
|
||||||
|
setEtcChecked(false)
|
||||||
|
}
|
||||||
|
setRoofInfo({ ...detailInfoData, [column]: value, [`${column}Etc`]: null })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBooleanRadioChange = (value: string) => {
|
||||||
|
if (value === '1') {
|
||||||
|
setRoofInfo({ ...detailInfoData, leakTrace: true })
|
||||||
|
} else {
|
||||||
|
setRoofInfo({ ...detailInfoData, leakTrace: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEtcInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value
|
||||||
|
setEtcVal(value)
|
||||||
|
setRoofInfo({ ...detailInfoData, [`${column}Etc`]: value })
|
||||||
|
}
|
||||||
|
|
||||||
// console.log('column: selectedId', column, selectedId)
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{radioEtcData[column as keyof typeof radioEtcData].map((item) => (
|
{radioEtcData[column as keyof typeof radioEtcData].map((item) => (
|
||||||
<div className="radio-form-box mb10" key={item.id}>
|
<div className="radio-form-box mb10" key={item.id}>
|
||||||
<input type="radio" name={column} id={`${item.id}`} disabled checked={Number(selectedId) === item.id} />
|
<input
|
||||||
<label htmlFor={`${item.id}`}>{item.label}</label>
|
type="radio"
|
||||||
|
name={column}
|
||||||
|
id={`${column}_${item.id}`}
|
||||||
|
disabled={mode === 'READ'}
|
||||||
|
checked={Number(selectedId) === item.id}
|
||||||
|
onChange={handleRadioChange}
|
||||||
|
value={item.id}
|
||||||
|
/>
|
||||||
|
<label htmlFor={`${column}_${item.id}`}>{item.label}</label>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{column !== 'rafterDirection' && column !== 'leakTrace' && column !== 'insulationPresence' && (
|
{column !== 'rafterDirection' && column !== 'leakTrace' && column !== 'insulationPresence' && (
|
||||||
<div className="radio-form-box mb10">
|
<div className="radio-form-box mb10">
|
||||||
<input type="radio" name={column} id={`${column}Etc`} value="etc" disabled checked={etcChecked} />
|
<input
|
||||||
|
type="radio"
|
||||||
|
name={column}
|
||||||
|
id={`${column}Etc`}
|
||||||
|
value="etc"
|
||||||
|
disabled={mode === 'READ'}
|
||||||
|
checked={etcChecked}
|
||||||
|
onChange={handleRadioChange}
|
||||||
|
/>
|
||||||
<label htmlFor={`${column}Etc`}>その他 (直接入力)</label>
|
<label htmlFor={`${column}Etc`}>その他 (直接入力)</label>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{etcChecked && (
|
{column !== 'leakTrace' && column !== 'rafterDirection' && (
|
||||||
<div className="data-input">
|
<div className="data-input">
|
||||||
<input type="text" className="input-frame" placeholder="-" readOnly value={etcValue?.toString() ?? ''} />
|
<input
|
||||||
|
type="text"
|
||||||
|
className="input-frame"
|
||||||
|
placeholder="-"
|
||||||
|
value={etcVal}
|
||||||
|
onChange={handleEtcInputChange}
|
||||||
|
disabled={mode === 'READ' || !etcChecked}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MultiCheck = ({
|
||||||
|
mode,
|
||||||
|
column,
|
||||||
|
roofInfo,
|
||||||
|
setRoofInfo,
|
||||||
|
}: {
|
||||||
|
mode: Mode
|
||||||
|
column: string
|
||||||
|
roofInfo: SurveyDetailInfo
|
||||||
|
setRoofInfo: (roofInfo: SurveyDetailRequest) => void
|
||||||
|
}) => {
|
||||||
|
const multiCheckData = column === 'supplementaryFacilities' ? supplementaryFacilities : roofMaterial
|
||||||
|
|
||||||
|
const [isOtherCheck, setIsOtherCheck] = useState<boolean>(false)
|
||||||
|
const [otherValue, setOtherValue] = useState<string>(roofInfo?.[`${column}Etc` as keyof SurveyDetailInfo]?.toString() ?? '')
|
||||||
|
|
||||||
|
const handleCheckbox = (id: number) => {
|
||||||
|
const value = makeNumArr(String(roofInfo[column as keyof SurveyDetailInfo] ?? ''))
|
||||||
|
const isOtherSelected = roofInfo?.[`${column}Etc` as keyof SurveyDetailInfo] !== null
|
||||||
|
|
||||||
|
let newValue: string[]
|
||||||
|
if (value.includes(String(id))) {
|
||||||
|
newValue = value.filter((v) => v !== String(id))
|
||||||
|
} else {
|
||||||
|
if (column === 'roofMaterial') {
|
||||||
|
const totalSelected = value.length + (isOtherSelected ? 1 : 0)
|
||||||
|
|
||||||
|
if (totalSelected >= 2) {
|
||||||
|
alert('屋根材は最大2個まで選択できます。')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newValue = [...value, String(id)]
|
||||||
|
}
|
||||||
|
setRoofInfo({ ...roofInfo, [column]: newValue.join(',') })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOtherCheckbox = () => {
|
||||||
|
if (column === 'roofMaterial') {
|
||||||
|
const value = makeNumArr(String(roofInfo[column as keyof SurveyDetailInfo] ?? ''))
|
||||||
|
const currentSelected = value.length
|
||||||
|
if (!isOtherCheck && currentSelected >= 2) {
|
||||||
|
alert('屋根材は最大2個まで選択できます。')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const newIsOtherCheck = !isOtherCheck
|
||||||
|
setIsOtherCheck(newIsOtherCheck)
|
||||||
|
setOtherValue('')
|
||||||
|
|
||||||
|
setRoofInfo({ ...roofInfo, [`${column}Etc`]: newIsOtherCheck ? '' : null })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOtherInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value
|
||||||
|
setOtherValue(value)
|
||||||
|
setRoofInfo({ ...roofInfo, [`${column}Etc`]: value })
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="data-check-wrap">
|
||||||
|
{multiCheckData.map((item) => (
|
||||||
|
<div className="check-form-box" key={item.id}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id={`${column}_${item.id}`}
|
||||||
|
checked={makeNumArr(String(roofInfo[column as keyof SurveyDetailInfo] ?? '')).includes(String(item.id))}
|
||||||
|
disabled={mode === 'READ'}
|
||||||
|
onChange={() => handleCheckbox(item.id)}
|
||||||
|
/>
|
||||||
|
<label htmlFor={`${column}_${item.id}`}>{item.name}</label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="check-form-box">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id={`${column}Etc`}
|
||||||
|
checked={roofInfo?.[`${column}Etc` as keyof SurveyDetailInfo] !== null}
|
||||||
|
disabled={mode === 'READ'}
|
||||||
|
onChange={handleOtherCheckbox}
|
||||||
|
/>
|
||||||
|
<label htmlFor={`${column}Etc`}>その他 (直接入力)</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="data-input">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="input-frame"
|
||||||
|
placeholder="-"
|
||||||
|
value={otherValue}
|
||||||
|
onChange={handleOtherInputChange}
|
||||||
|
disabled={mode === 'READ' || !isOtherCheck}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { useRouter } from 'next/navigation'
|
|||||||
import SearchForm from './SearchForm'
|
import SearchForm from './SearchForm'
|
||||||
import { useSurveyFilterStore } from '@/store/surveyFilterStore'
|
import { useSurveyFilterStore } from '@/store/surveyFilterStore'
|
||||||
import { useSessionStore } from '@/store/session'
|
import { useSessionStore } from '@/store/session'
|
||||||
import { SurveyBasicInfo } from '@/types/Survey'
|
import type { SurveyBasicInfo } from '@/types/Survey'
|
||||||
|
|
||||||
export default function ListTable() {
|
export default function ListTable() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|||||||
@ -17,7 +17,6 @@ export default function SearchForm({ memberRole, userId }: { memberRole: string;
|
|||||||
}
|
}
|
||||||
setKeyword(searchKeyword)
|
setKeyword(searchKeyword)
|
||||||
setSearchOption(option)
|
setSearchOption(option)
|
||||||
// onItemsInit()
|
|
||||||
}
|
}
|
||||||
const searchOptions = memberRole === 'Partner' ? SEARCH_OPTIONS_PARTNERS : SEARCH_OPTIONS
|
const searchOptions = memberRole === 'Partner' ? SEARCH_OPTIONS_PARTNERS : SEARCH_OPTIONS
|
||||||
|
|
||||||
@ -38,7 +37,6 @@ export default function SearchForm({ memberRole, userId }: { memberRole: string;
|
|||||||
if (e.target.value === 'all') {
|
if (e.target.value === 'all') {
|
||||||
setKeyword('')
|
setKeyword('')
|
||||||
setSearchKeyword('')
|
setSearchKeyword('')
|
||||||
// onItemsInit()
|
|
||||||
setSearchOption('all')
|
setSearchOption('all')
|
||||||
setOption('all')
|
setOption('all')
|
||||||
} else {
|
} else {
|
||||||
@ -80,7 +78,6 @@ export default function SearchForm({ memberRole, userId }: { memberRole: string;
|
|||||||
checked={isMySurvey === userId}
|
checked={isMySurvey === userId}
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
setIsMySurvey(isMySurvey === userId ? null : userId)
|
setIsMySurvey(isMySurvey === userId ? null : userId)
|
||||||
// onItemsInit()
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="ch01">私が書いた物件</label>
|
<label htmlFor="ch01">私が書いた物件</label>
|
||||||
@ -94,7 +91,6 @@ export default function SearchForm({ memberRole, userId }: { memberRole: string;
|
|||||||
value={sort}
|
value={sort}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setSort(e.target.value as 'created' | 'updated')
|
setSort(e.target.value as 'created' | 'updated')
|
||||||
// onItemsInit()
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option value="created">最近の登録日</option>
|
<option value="created">最近の登録日</option>
|
||||||
|
|||||||
@ -2,19 +2,27 @@ import { useQuery } from '@tanstack/react-query'
|
|||||||
import { axiosInstance, transformObjectKeys } from '@/libs/axios'
|
import { axiosInstance, transformObjectKeys } from '@/libs/axios'
|
||||||
import { useSuitableStore } from '@/store/useSuitableStore'
|
import { useSuitableStore } from '@/store/useSuitableStore'
|
||||||
import { useCommCode } from './useCommCode'
|
import { useCommCode } from './useCommCode'
|
||||||
import { SUITABLE_HEAD_CODE, type SuitableDetailGroup, type SuitableMain, type Suitable, type SuitableDetail } from '@/types/Suitable'
|
import { SUITABLE_HEAD_CODE, type Suitable, type SuitableDetail } from '@/types/Suitable'
|
||||||
|
|
||||||
export function useSuitable() {
|
export function useSuitable() {
|
||||||
const { getCommCode } = useCommCode()
|
const { getCommCode } = useCommCode()
|
||||||
const { selectedCategory, searchValue, suitableCommCode, setSuitableCommCode, isSearch } = useSuitableStore()
|
const { selectedCategory, searchValue, suitableCommCode, setSuitableCommCode, isSearch } = useSuitableStore()
|
||||||
|
|
||||||
const getSuitables = async (): Promise<Suitable> => {
|
const getSuitables = async (): Promise<Suitable[]> => {
|
||||||
try {
|
try {
|
||||||
const response = await axiosInstance(null).get<Suitable>('/api/suitable/list')
|
const response = await axiosInstance(null).get<Suitable[]>('/api/suitable/list', {
|
||||||
|
params: {
|
||||||
|
pageNumber: 1,
|
||||||
|
itemPerPage: 1000,
|
||||||
|
ids: '',
|
||||||
|
category: '',
|
||||||
|
keyword: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
return response.data
|
return response.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('지붕재 데이터 로드 실패:', error)
|
console.error('지붕재 데이터 로드 실패:', error)
|
||||||
return { suitable: [], detail: [] }
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,12 +50,8 @@ export function useSuitable() {
|
|||||||
return commCode?.find((item) => item.code === code)?.codeJp || ''
|
return commCode?.find((item) => item.code === code)?.codeJp || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const toSuitableDetail = (mainId: number): SuitableDetail[] => {
|
const toSuitableDetail = (suitableDetailString: string): SuitableDetail[] => {
|
||||||
try {
|
try {
|
||||||
const suitableDetailString = suitableList?.detail.find((item) => item.mainId === mainId)?.detail
|
|
||||||
if (!suitableDetailString) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
const suitableDetailArray = transformObjectKeys(JSON.parse(suitableDetailString)) as SuitableDetail[]
|
const suitableDetailArray = transformObjectKeys(JSON.parse(suitableDetailString)) as SuitableDetail[]
|
||||||
if (!Array.isArray(suitableDetailArray)) {
|
if (!Array.isArray(suitableDetailArray)) {
|
||||||
throw new Error('suitableDetailArray is not an array')
|
throw new Error('suitableDetailArray is not an array')
|
||||||
@ -59,7 +63,7 @@ export function useSuitable() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: suitableList, isLoading: isInitialLoading } = useQuery<Suitable>({
|
const { data: suitableList, isLoading: isInitialLoading } = useQuery<Suitable[]>({
|
||||||
queryKey: ['suitables', 'list'],
|
queryKey: ['suitables', 'list'],
|
||||||
queryFn: async () => await getSuitables(),
|
queryFn: async () => await getSuitables(),
|
||||||
staleTime: 1000 * 60 * 10, // 10분
|
staleTime: 1000 * 60 * 10, // 10분
|
||||||
@ -70,23 +74,19 @@ export function useSuitable() {
|
|||||||
data: suitableSearchResults,
|
data: suitableSearchResults,
|
||||||
refetch: refetchBySearch,
|
refetch: refetchBySearch,
|
||||||
isLoading: isSearchLoading,
|
isLoading: isSearchLoading,
|
||||||
} = useQuery<Suitable>({
|
} = useQuery<Suitable[]>({
|
||||||
queryKey: ['suitables', 'search', selectedCategory, isSearch],
|
queryKey: ['suitables', 'search', selectedCategory, isSearch],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!isSearch && !selectedCategory) {
|
if (!isSearch && !selectedCategory) {
|
||||||
// 검색 상태가 아니면 초기 데이터 반환 임시처리
|
return isInitialLoading ? await getSuitables() : suitableList ?? [] // 검색 상태가 아니면 초기 데이터 반환 임시처리
|
||||||
return isInitialLoading ? await getSuitables() : suitableList ?? { suitable: [], detail: [] }
|
|
||||||
} else {
|
} else {
|
||||||
const filteredSuitable = suitableList?.suitable.filter((item: SuitableMain) => {
|
return (
|
||||||
const categoryMatch = !selectedCategory || item.roofMtCd === selectedCategory
|
suitableList?.filter((item: Suitable) => {
|
||||||
const searchMatch = !searchValue || item.productName.includes(searchValue)
|
const categoryMatch = !selectedCategory || item.roofMtCd === selectedCategory
|
||||||
return categoryMatch && searchMatch
|
const searchMatch = !searchValue || item.productName.includes(searchValue)
|
||||||
}) ?? []
|
return categoryMatch && searchMatch
|
||||||
const mainIds = filteredSuitable.map((item: SuitableMain) => item.id)
|
}) ?? []
|
||||||
const filteredDetail = suitableList?.detail.filter((item: SuitableDetailGroup) => {
|
)
|
||||||
return mainIds.includes(item.mainId)
|
|
||||||
}) ?? []
|
|
||||||
return { suitable: filteredSuitable, detail: filteredDetail }
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
staleTime: 1000 * 60 * 10,
|
staleTime: 1000 * 60 * 10,
|
||||||
|
|||||||
@ -1,109 +0,0 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
|
||||||
import { axiosInstance, transformObjectKeys } from '@/libs/axios'
|
|
||||||
import { useSuitableStore } from '@/store/useSuitableStore'
|
|
||||||
import { useCommCode } from './useCommCode'
|
|
||||||
import { SUITABLE_HEAD_CODE, type SuitableDetail } from '@/types/Suitable'
|
|
||||||
|
|
||||||
export type Suitable = {
|
|
||||||
id: number
|
|
||||||
productName: string
|
|
||||||
manuFtCd: string
|
|
||||||
roofMtCd: string
|
|
||||||
roofShCd: string
|
|
||||||
detail: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useSuitableRaw() {
|
|
||||||
const { getCommCode } = useCommCode()
|
|
||||||
const { selectedCategory, searchValue, suitableCommCode, setSuitableCommCode, isSearch } = useSuitableStore()
|
|
||||||
|
|
||||||
const getSuitables = async (): Promise<Suitable[]> => {
|
|
||||||
try {
|
|
||||||
const response = await axiosInstance(null).get<Suitable[]>('/api/suitable/list/test')
|
|
||||||
return response.data
|
|
||||||
} catch (error) {
|
|
||||||
console.error('지붕재 데이터 로드 실패:', error)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// const updateSearchResults = async (selectedCategory: string | undefined, searchValue: string | undefined): Promise<SuitableData[]> => {
|
|
||||||
// try {
|
|
||||||
// const response = await axiosInstance(null).get<SuitableData[]>('/api/suitable/list', { params: { selectedCategory, searchValue } })
|
|
||||||
// return response.data
|
|
||||||
// } catch (error) {
|
|
||||||
// console.error('지붕재 데이터 검색 실패:', error)
|
|
||||||
// return []
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
const getSuitableCommCode = () => {
|
|
||||||
const headCodes = Object.values(SUITABLE_HEAD_CODE) as SUITABLE_HEAD_CODE[]
|
|
||||||
for (const code of headCodes) {
|
|
||||||
getCommCode(code).then((res) => {
|
|
||||||
setSuitableCommCode(code, res)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const toCodeName = (headCode: string, code: string): string => {
|
|
||||||
const commCode = suitableCommCode.get(headCode)
|
|
||||||
return commCode?.find((item) => item.code === code)?.codeJp || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const toSuitableDetail = (suitableDetailString: string): SuitableDetail[] => {
|
|
||||||
try {
|
|
||||||
const suitableDetailArray = transformObjectKeys(JSON.parse(suitableDetailString)) as SuitableDetail[]
|
|
||||||
if (!Array.isArray(suitableDetailArray)) {
|
|
||||||
throw new Error('suitableDetailArray is not an array')
|
|
||||||
}
|
|
||||||
return suitableDetailArray
|
|
||||||
} catch (error) {
|
|
||||||
console.error('지붕재 데이터 파싱 실패:', error)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data: suitableList, isLoading: isInitialLoading } = useQuery<Suitable[]>({
|
|
||||||
queryKey: ['suitables', 'list'],
|
|
||||||
queryFn: async () => await getSuitables(),
|
|
||||||
staleTime: 1000 * 60 * 10, // 10분
|
|
||||||
gcTime: 1000 * 60 * 10, // 10분
|
|
||||||
})
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: suitableSearchResults,
|
|
||||||
refetch: refetchBySearch,
|
|
||||||
isLoading: isSearchLoading,
|
|
||||||
// } = useQuery<SuitableData>({
|
|
||||||
} = useQuery<Suitable[]>({
|
|
||||||
queryKey: ['suitables', 'search', selectedCategory, isSearch],
|
|
||||||
queryFn: async () => {
|
|
||||||
if (!isSearch && !selectedCategory) {
|
|
||||||
return isInitialLoading ? await getSuitables() : suitableList ?? [] // 검색 상태가 아니면 초기 데이터 반환 임시처리
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
suitableList?.filter((item: Suitable) => {
|
|
||||||
const categoryMatch = !selectedCategory || item.roofMtCd === selectedCategory
|
|
||||||
const searchMatch = !searchValue || item.productName.includes(searchValue)
|
|
||||||
return categoryMatch && searchMatch
|
|
||||||
}) ?? []
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
staleTime: 1000 * 60 * 10,
|
|
||||||
gcTime: 1000 * 60 * 10,
|
|
||||||
enabled: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
getSuitables,
|
|
||||||
getSuitableCommCode,
|
|
||||||
toCodeName,
|
|
||||||
toSuitableDetail,
|
|
||||||
suitableList,
|
|
||||||
suitableSearchResults,
|
|
||||||
refetchBySearch,
|
|
||||||
isSearchLoading,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -7,7 +7,7 @@ import { useSessionStore } from '@/store/session'
|
|||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { AxiosResponse } from 'axios'
|
import { AxiosResponse } from 'axios'
|
||||||
|
|
||||||
const requiredFields = [
|
export const requiredFields = [
|
||||||
{
|
{
|
||||||
field: 'installationSystem',
|
field: 'installationSystem',
|
||||||
name: '設置希望システム',
|
name: '設置希望システム',
|
||||||
@ -67,7 +67,7 @@ export function useServey(id?: number): {
|
|||||||
createSurveyDetail: (params: { surveyId: number; surveyDetail: SurveyDetailCoverRequest }) => void
|
createSurveyDetail: (params: { surveyId: number; surveyDetail: SurveyDetailCoverRequest }) => void
|
||||||
updateSurvey: (survey: SurveyRegistRequest) => void
|
updateSurvey: (survey: SurveyRegistRequest) => void
|
||||||
deleteSurvey: () => Promise<boolean>
|
deleteSurvey: () => Promise<boolean>
|
||||||
submitSurvey: () => void
|
submitSurvey: (saveId?: number) => void
|
||||||
validateSurveyDetail: (surveyDetail: SurveyDetailRequest) => string
|
validateSurveyDetail: (surveyDetail: SurveyDetailRequest) => string
|
||||||
getZipCode: (zipCode: string) => Promise<ZipCode[] | null>
|
getZipCode: (zipCode: string) => Promise<ZipCode[] | null>
|
||||||
refetchSurveyList: () => void
|
refetchSurveyList: () => void
|
||||||
@ -77,7 +77,7 @@ export function useServey(id?: number): {
|
|||||||
const { session } = useSessionStore()
|
const { session } = useSessionStore()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data: surveyListData,
|
||||||
isLoading: isLoadingSurveyList,
|
isLoading: isLoadingSurveyList,
|
||||||
refetch: refetchSurveyList,
|
refetch: refetchSurveyList,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
@ -100,18 +100,17 @@ export function useServey(id?: number): {
|
|||||||
enabled: session?.isLoggedIn,
|
enabled: session?.isLoggedIn,
|
||||||
})
|
})
|
||||||
const surveyData = useMemo(() => {
|
const surveyData = useMemo(() => {
|
||||||
if (!data) return {}
|
if (!surveyListData) return { count: 0, data: [] }
|
||||||
return {
|
return {
|
||||||
data: data.data,
|
...surveyListData,
|
||||||
count: data.count,
|
|
||||||
}
|
}
|
||||||
}, [data])
|
}, [surveyListData])
|
||||||
|
|
||||||
const { data: surveyDetail, isLoading: isLoadingSurveyDetail } = useQuery({
|
const { data: surveyDetail, isLoading: isLoadingSurveyDetail } = useQuery({
|
||||||
queryKey: ['survey', id],
|
queryKey: ['survey', id],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (id === undefined) throw new Error('id is required')
|
if (id === undefined) throw new Error('id is required')
|
||||||
if (id === null) return null
|
if (id === null || isNaN(id)) return null
|
||||||
const resp = await axiosInstance(null).get<SurveyBasicInfo>(`/api/survey-sales/${id}`)
|
const resp = await axiosInstance(null).get<SurveyBasicInfo>(`/api/survey-sales/${id}`)
|
||||||
return resp.data
|
return resp.data
|
||||||
},
|
},
|
||||||
@ -132,6 +131,7 @@ export function useServey(id?: number): {
|
|||||||
|
|
||||||
const { mutate: updateSurvey, isPending: isUpdatingSurvey } = useMutation({
|
const { mutate: updateSurvey, isPending: isUpdatingSurvey } = useMutation({
|
||||||
mutationFn: async (survey: SurveyRegistRequest) => {
|
mutationFn: async (survey: SurveyRegistRequest) => {
|
||||||
|
console.log('updateSurvey, survey:: ', survey)
|
||||||
if (id === undefined) throw new Error('id is required')
|
if (id === undefined) throw new Error('id is required')
|
||||||
const resp = await axiosInstance(null).put<SurveyRegistRequest>(`/api/survey-sales/${id}`, survey)
|
const resp = await axiosInstance(null).put<SurveyRegistRequest>(`/api/survey-sales/${id}`, survey)
|
||||||
return resp.data
|
return resp.data
|
||||||
@ -166,9 +166,10 @@ export function useServey(id?: number): {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const { mutateAsync: submitSurvey } = useMutation({
|
const { mutateAsync: submitSurvey } = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async (saveId?: number) => {
|
||||||
if (id === undefined) throw new Error('id is required')
|
const submitId = saveId ?? id
|
||||||
const resp = await axiosInstance(null).patch<boolean>(`/api/survey-sales/${id}`, {
|
if (!submitId) throw new Error('id is required')
|
||||||
|
const resp = await axiosInstance(null).patch<boolean>(`/api/survey-sales/${submitId}`, {
|
||||||
submit: true,
|
submit: true,
|
||||||
})
|
})
|
||||||
return resp.data
|
return resp.data
|
||||||
@ -180,12 +181,22 @@ export function useServey(id?: number): {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const validateSurveyDetail = (surveyDetail: SurveyDetailRequest) => {
|
const validateSurveyDetail = (surveyDetail: SurveyDetailRequest) => {
|
||||||
const etcFields = ['installationSystem', 'constructionYear', 'rafterSize', 'rafterPitch', 'waterproofMaterial', 'structureOrder'] as const
|
const etcFields = [
|
||||||
|
'installationSystem',
|
||||||
|
'constructionYear',
|
||||||
|
'rafterSize',
|
||||||
|
'rafterPitch',
|
||||||
|
'waterproofMaterial',
|
||||||
|
'structureOrder',
|
||||||
|
'insulationPresence',
|
||||||
|
] as const
|
||||||
|
|
||||||
const emptyField = requiredFields.find((field) => {
|
const emptyField = requiredFields.find((field) => {
|
||||||
if (etcFields.includes(field.field as (typeof etcFields)[number])) {
|
if (etcFields.includes(field.field as (typeof etcFields)[number])) {
|
||||||
return (
|
return (
|
||||||
surveyDetail[field.field as keyof SurveyDetailRequest] === null && surveyDetail[`${field.field}_ETC` as keyof SurveyDetailRequest] === ''
|
surveyDetail[field.field as keyof SurveyDetailRequest] === null &&
|
||||||
|
(surveyDetail[`${field.field}Etc` as keyof SurveyDetailRequest] === null ||
|
||||||
|
surveyDetail[`${field.field}Etc` as keyof SurveyDetailRequest]?.toString().trim() === '')
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return surveyDetail[field.field as keyof SurveyDetailRequest] === null
|
return surveyDetail[field.field as keyof SurveyDetailRequest] === null
|
||||||
@ -197,7 +208,7 @@ export function useServey(id?: number): {
|
|||||||
return 'contractCapacityUnit'
|
return 'contractCapacityUnit'
|
||||||
}
|
}
|
||||||
|
|
||||||
return emptyField?.name || ''
|
return emptyField?.field || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const getZipCode = async (zipCode: string): Promise<ZipCode[] | null> => {
|
const getZipCode = async (zipCode: string): Promise<ZipCode[] | null> => {
|
||||||
@ -213,7 +224,7 @@ export function useServey(id?: number): {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
surveyList: surveyData,
|
surveyList: surveyData.data,
|
||||||
surveyDetail: surveyDetail as SurveyBasicInfo | null,
|
surveyDetail: surveyDetail as SurveyBasicInfo | null,
|
||||||
isLoadingSurveyList,
|
isLoadingSurveyList,
|
||||||
isLoadingSurveyDetail,
|
isLoadingSurveyDetail,
|
||||||
|
|||||||
@ -84,10 +84,6 @@ export const transformObjectKeys = (obj: any): any => {
|
|||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
export const camelToSnake = (str: string): string => {
|
|
||||||
return str.replace(/([A-Z])/g, (group) => `_${group.toLowerCase()}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const snakeToCamel = (str: string): string => {
|
const snakeToCamel = (str: string): string => {
|
||||||
return str.toLowerCase().replace(/([-_][a-z])/g, (group) => group.toUpperCase().replace('-', '').replace('_', ''))
|
return str.toLowerCase().replace(/([-_][a-z])/g, (group) => group.toUpperCase().replace('-', '').replace('_', ''))
|
||||||
}
|
}
|
||||||
|
|||||||
38
src/libs/partner.tsx
Normal file
38
src/libs/partner.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { createPool } from 'mysql2'
|
||||||
|
|
||||||
|
const pool = createPool({
|
||||||
|
host: process.env.DB_HOST as string,
|
||||||
|
user: process.env.DB_USER as string,
|
||||||
|
password: process.env.DB_PASSWORD as string,
|
||||||
|
database: process.env.DB_DATABASE as string,
|
||||||
|
port: Number(process.env.DB_PORT),
|
||||||
|
waitForConnections: true,
|
||||||
|
connectionLimit: 10,
|
||||||
|
queueLimit: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
pool.getConnection((err, conn) => {
|
||||||
|
if (err) console.log('Error connecting to db...')
|
||||||
|
else console.log('Connected to db...!')
|
||||||
|
conn.release()
|
||||||
|
})
|
||||||
|
|
||||||
|
const executeQuery = (query: string, arrParams: any[]) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
pool.query(query, arrParams, (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
console.log('🚀 ~ pool.query ~ err:', err)
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
console.log('🚀 ~ pool.query ~ data:', data)
|
||||||
|
resolve(data)
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.log('🚀 ~ returnnewPromise ~ err:', err)
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default executeQuery
|
||||||
@ -7,6 +7,7 @@ import { useHeaderStore } from '@/store/header'
|
|||||||
import { usePopupController } from '@/store/popupController'
|
import { usePopupController } from '@/store/popupController'
|
||||||
import { useSideNavState } from '@/store/sideNavState'
|
import { useSideNavState } from '@/store/sideNavState'
|
||||||
import { useSessionStore } from '@/store/session'
|
import { useSessionStore } from '@/store/session'
|
||||||
|
import { tracking } from '@/libs/tracking'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@ -27,6 +28,17 @@ export default function EdgeProvider({ children, sessionData }: EdgeProviderProp
|
|||||||
const { setAlertMsg, setAlertBtn, setAlert, setAlert2, setAlert2BtnYes, setAlert2BtnNo } = usePopupController()
|
const { setAlertMsg, setAlertBtn, setAlert, setAlert2, setAlert2BtnYes, setAlert2BtnNo } = usePopupController()
|
||||||
const { session, setSession } = useSessionStore()
|
const { session, setSession } = useSessionStore()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자 이벤트 트래킹 처리
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const handlePageEvent = (path: string) => {
|
||||||
|
tracking({
|
||||||
|
url: path,
|
||||||
|
data: '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* alert 함수 - window.alert 함수 대체
|
* alert 함수 - window.alert 함수 대체
|
||||||
* @param msg
|
* @param msg
|
||||||
@ -88,6 +100,8 @@ export default function EdgeProvider({ children, sessionData }: EdgeProviderProp
|
|||||||
}
|
}
|
||||||
//사이드바 초기화
|
//사이드바 초기화
|
||||||
reset()
|
reset()
|
||||||
|
// 페이지 이벤트 트래킹
|
||||||
|
// handlePageEvent(pathname)
|
||||||
}, [pathname])
|
}, [pathname])
|
||||||
|
|
||||||
return <>{children}</>
|
return <>{children}</>
|
||||||
|
|||||||
@ -25,20 +25,11 @@ export type SuitableDetail = {
|
|||||||
memo: string
|
memo: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// export type Suitable = {
|
export type Suitable = {
|
||||||
// id: number
|
id: number
|
||||||
// productName: string
|
productName: string
|
||||||
// manuFtCd: string
|
manuFtCd: string
|
||||||
// roofMtCd: string
|
roofMtCd: string
|
||||||
// roofShCd: string
|
roofShCd: string
|
||||||
// detail: string
|
|
||||||
// }
|
|
||||||
|
|
||||||
export type SuitableDetailGroup = {
|
|
||||||
mainId: number
|
|
||||||
detail: string
|
detail: string
|
||||||
}
|
}
|
||||||
export type Suitable = {
|
|
||||||
suitable: SuitableMain[]
|
|
||||||
detail: SuitableDetailGroup[]
|
|
||||||
}
|
|
||||||
@ -185,3 +185,28 @@ export const isEqualObjects = (obj1, obj2) => {
|
|||||||
function isObject(value) {
|
function isObject(value) {
|
||||||
return value !== null && typeof value === 'object'
|
return value !== null && typeof value === 'object'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 카멜케이스를 스네이크케이스로 변환하는 함수
|
||||||
|
export const toSnakeCase = (str) => {
|
||||||
|
return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 객체의 키를 스네이크케이스로 변환하는 함수
|
||||||
|
export const convertToSnakeCase = (obj) => {
|
||||||
|
if (obj === null || obj === undefined) return obj;
|
||||||
|
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
return obj.map((item) => convertToSnakeCase(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof obj === 'object') {
|
||||||
|
return Object.keys(obj).reduce((acc, key) => {
|
||||||
|
const snakeKey = toSnakeCase(key).toUpperCase();
|
||||||
|
acc[snakeKey] = convertToSnakeCase(obj[key]);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user