Compare commits

..

29 Commits

Author SHA1 Message Date
574de12908 feat: 지붕재적합성 pdf 다운로드 api (ssr) 방식 추가 2025-06-04 16:46:27 +09:00
0bf5b57a98 Merge pull request 'feature/survey' (#56) from feature/survey into dev
Reviewed-on: #56
2025-06-04 16:31:00 +09:00
9b7c32ac04 fix: add spinning when generate pdf 2025-06-04 16:28:09 +09:00
9a91727c98 fix: modify error handling when status 403 2025-06-04 15:47:47 +09:00
50617c7b7f fix: fix rendering issue
- 매물 수정/작성 시 담당자명, 판매점명, 시공점명 랜더링 안되는 문제 해결
- T01 계정 수정 버튼 랜더링 안되는 문제 해결
2025-06-04 13:28:42 +09:00
0c27c19341 refactor: modify member permission verification logic
- 매물 조회 권한 검증 로직 리팩토링
2025-06-04 10:51:11 +09:00
e756465250 feat: enhance survey sales API and component integration
- 조사매물 조회 시 권한 확인 로직 추가
2025-06-02 18:09:42 +09:00
c6664e9827 Merge branch 'dev' of https://git.hanasys.jp/qcast3/onsitesurvey into feature/survey 2025-06-02 15:46:18 +09:00
1bddc86bcf chore: enhance survey detail validation
- 건축연수, 단열재의 유무 필드 기타 항목 유효성 검사 추가
- ORDER, MUSUBI 조사매물 목록 검색조건에 판매점Id, 시공점Id 추가
- T01 계정의 경우 제출받은 매물만 수정/삭제 가능하도록 수정
-
2025-06-02 15:46:07 +09:00
5da4417bb5 style: refine PDF component layout and styles for improved presentation
- Removed unnecessary margin from the creation time display in SuitableDownloadPdf for better alignment.
- Added flexbox properties to the PDF table content for enhanced structure and height consistency.
2025-06-02 15:42:27 +09:00
9d89ceadc0 style: update PDF view and subcomponent styles for consistency and responsiveness
- Added a wrapper for PDF tables with fixed width for better layout.
- Adjusted height and flex properties in the PDF intro page for improved structure.
- Enhanced spacing and alignment in various components for better visual consistency.
- Standardized color codes and formatting across styles for uniformity.
- Implemented responsive design adjustments for smaller screens.
2025-06-02 15:30:17 +09:00
404eaa3299 feat: add creation time display to SuitableDownloadPdf component
- Included creation time in the footer of the PDF content for better context.
- Ensured consistent display of creation time in both main and table content sections.
2025-06-02 15:25:41 +09:00
1bba8b1af0 Merge branch 'dev' of https://git.hanasys.jp/qcast3/onsitesurvey into feature/survey 2025-06-02 13:33:35 +09:00
749dd30ecb refactor: update survey submission handling and improve data validation 2025-06-02 13:33:23 +09:00
46e6bc36f8 Merge branch 'dev' of https://git.hanasys.jp/qcast3/onsitesurvey into feature/survey 2025-06-02 10:16:10 +09:00
454a8a39a9 refactor: rename store to storeId in API and components for consistency 2025-06-02 10:16:00 +09:00
4ed8a78192 Merge branch 'dev' of https://git.hanasys.jp/qcast3/onsitesurvey into feature/survey 2025-05-30 17:50:43 +09:00
39645affa6 feat: add contructionPointId, submissionTargetNm column in survey_basic model 2025-05-30 17:15:45 +09:00
1022ef7023 Merge branch 'dev' of https://git.hanasys.jp/qcast3/onsitesurvey into feature/survey 2025-05-30 16:53:30 +09:00
05f0abb194 fix: solve rendering component error 2025-05-30 16:53:22 +09:00
14ba63e2b5 Merge branch 'dev' of https://git.hanasys.jp/qcast3/onsitesurvey into feature/survey 2025-05-30 16:23:04 +09:00
dcb7bc2ff4 fix: adjust required fields in SurveySaleSubmitPopup based on user role 2025-05-30 16:22:58 +09:00
7fbaf4a1e0 fix: update SurveySaleSubmitPopup title 2025-05-30 14:15:53 +09:00
68a0c15c61 Merge branch 'dev' of https://git.hanasys.jp/qcast3/onsitesurvey into feature/survey 2025-05-30 13:58:51 +09:00
799b0025c4 feat: add spinning when create/update/delete survey & fix rules for displaying store, contructionPoint input 2025-05-30 13:58:41 +09:00
5cc1cc9247 chore: update env to use localhost for API URLs and refactor SurveySaleDownloadPdf and SurveySaleSubmitPopup components 2025-05-30 10:04:40 +09:00
c9b3909a6b Merge branch 'dev' of https://git.hanasys.jp/qcast3/onsitesurvey into feature/survey 2025-05-29 16:33:15 +09:00
46720229fc feat: add postcode popup page publish
- 조회 된 데이터 없을 때의 tr 추가
2025-05-29 08:59:09 +09:00
73d93d05f9 feat: add publish at surveyDetail page (structure order), submit popup page 2025-05-28 18:08:25 +09:00
25 changed files with 1524 additions and 594 deletions

View File

@ -6,6 +6,7 @@ const nextConfig: NextConfig = {
sassOptions: { sassOptions: {
includePaths: [path.join(__dirname, './src/styles')], includePaths: [path.join(__dirname, './src/styles')],
}, },
serverExternalPackages: ['@react-pdf/renderer'],
async rewrites() { async rewrites() {
return [ return [
{ {

480
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@prisma/client": "^6.7.0", "@prisma/client": "^6.7.0",
"@react-pdf/renderer": "^4.3.0",
"@tanstack/react-query": "^5.71.0", "@tanstack/react-query": "^5.71.0",
"@tanstack/react-query-devtools": "^5.71.0", "@tanstack/react-query-devtools": "^5.71.0",
"@types/nodemailer": "^6.4.17", "@types/nodemailer": "^6.4.17",
@ -1637,6 +1638,180 @@
"@prisma/debug": "6.7.0" "@prisma/debug": "6.7.0"
} }
}, },
"node_modules/@react-pdf/fns": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@react-pdf/fns/-/fns-3.1.2.tgz",
"integrity": "sha512-qTKGUf0iAMGg2+OsUcp9ffKnKi41RukM/zYIWMDJ4hRVYSr89Q7e3wSDW/Koqx3ea3Uy/z3h2y3wPX6Bdfxk6g==",
"license": "MIT"
},
"node_modules/@react-pdf/font": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@react-pdf/font/-/font-4.0.2.tgz",
"integrity": "sha512-/dAWu7Y2RD1RxarDZ9SkYPHgBYOhmcDnet4W/qN/m8k+A2Hr3ja54GymSR7GGxWBtxjKtNauVKrTa9LS1n8WUw==",
"license": "MIT",
"dependencies": {
"@react-pdf/pdfkit": "^4.0.3",
"@react-pdf/types": "^2.9.0",
"fontkit": "^2.0.2",
"is-url": "^1.2.4"
}
},
"node_modules/@react-pdf/image": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@react-pdf/image/-/image-3.0.3.tgz",
"integrity": "sha512-lvP5ryzYM3wpbO9bvqLZYwEr5XBDX9jcaRICvtnoRqdJOo7PRrMnmB4MMScyb+Xw10mGeIubZAAomNAG5ONQZQ==",
"license": "MIT",
"dependencies": {
"@react-pdf/png-js": "^3.0.0",
"jay-peg": "^1.1.1"
}
},
"node_modules/@react-pdf/layout": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@react-pdf/layout/-/layout-4.4.0.tgz",
"integrity": "sha512-Aq+Cc6JYausWLoks2FvHe3PwK9cTuvksB2uJ0AnkKJEUtQbvCq8eCRb1bjbbwIji9OzFRTTzZij7LzkpKHjIeA==",
"license": "MIT",
"dependencies": {
"@react-pdf/fns": "3.1.2",
"@react-pdf/image": "^3.0.3",
"@react-pdf/primitives": "^4.1.1",
"@react-pdf/stylesheet": "^6.1.0",
"@react-pdf/textkit": "^6.0.0",
"@react-pdf/types": "^2.9.0",
"emoji-regex": "^10.3.0",
"queue": "^6.0.1",
"yoga-layout": "^3.2.1"
}
},
"node_modules/@react-pdf/pdfkit": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@react-pdf/pdfkit/-/pdfkit-4.0.3.tgz",
"integrity": "sha512-k+Lsuq8vTwWsCqTp+CCB4+2N+sOTFrzwGA7aw3H9ix/PDWR9QksbmNg0YkzGbLAPI6CeawmiLHcf4trZ5ecLPQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@react-pdf/png-js": "^3.0.0",
"browserify-zlib": "^0.2.0",
"crypto-js": "^4.2.0",
"fontkit": "^2.0.2",
"jay-peg": "^1.1.1",
"linebreak": "^1.1.0",
"vite-compatible-readable-stream": "^3.6.1"
}
},
"node_modules/@react-pdf/png-js": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@react-pdf/png-js/-/png-js-3.0.0.tgz",
"integrity": "sha512-eSJnEItZ37WPt6Qv5pncQDxLJRK15eaRwPT+gZoujP548CodenOVp49GST8XJvKMFt9YqIBzGBV/j9AgrOQzVA==",
"license": "MIT",
"dependencies": {
"browserify-zlib": "^0.2.0"
}
},
"node_modules/@react-pdf/primitives": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@react-pdf/primitives/-/primitives-4.1.1.tgz",
"integrity": "sha512-IuhxYls1luJb7NUWy6q5avb1XrNaVj9bTNI40U9qGRuS6n7Hje/8H8Qi99Z9UKFV74bBP3DOf3L1wV2qZVgVrQ==",
"license": "MIT"
},
"node_modules/@react-pdf/reconciler": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@react-pdf/reconciler/-/reconciler-1.1.4.tgz",
"integrity": "sha512-oTQDiR/t4Z/Guxac88IavpU2UgN7eR0RMI9DRKvKnvPz2DUasGjXfChAdMqDNmJJxxV26mMy9xQOUV2UU5/okg==",
"license": "MIT",
"dependencies": {
"object-assign": "^4.1.1",
"scheduler": "0.25.0-rc-603e6108-20241029"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@react-pdf/reconciler/node_modules/scheduler": {
"version": "0.25.0-rc-603e6108-20241029",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-603e6108-20241029.tgz",
"integrity": "sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==",
"license": "MIT"
},
"node_modules/@react-pdf/render": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@react-pdf/render/-/render-4.3.0.tgz",
"integrity": "sha512-MdWfWaqO6d7SZD75TZ2z5L35V+cHpyA43YNRlJNG0RJ7/MeVGDQv12y/BXOJgonZKkeEGdzM3EpAt9/g4E22WA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@react-pdf/fns": "3.1.2",
"@react-pdf/primitives": "^4.1.1",
"@react-pdf/textkit": "^6.0.0",
"@react-pdf/types": "^2.9.0",
"abs-svg-path": "^0.1.1",
"color-string": "^1.9.1",
"normalize-svg-path": "^1.1.0",
"parse-svg-path": "^0.1.2",
"svg-arc-to-cubic-bezier": "^3.2.0"
}
},
"node_modules/@react-pdf/renderer": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@react-pdf/renderer/-/renderer-4.3.0.tgz",
"integrity": "sha512-28gpA69fU9ZQrDzmd5xMJa1bDf8t0PT3ApUKBl2PUpoE/x4JlvCB5X66nMXrfFrgF2EZrA72zWQAkvbg7TE8zw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@react-pdf/fns": "3.1.2",
"@react-pdf/font": "^4.0.2",
"@react-pdf/layout": "^4.4.0",
"@react-pdf/pdfkit": "^4.0.3",
"@react-pdf/primitives": "^4.1.1",
"@react-pdf/reconciler": "^1.1.4",
"@react-pdf/render": "^4.3.0",
"@react-pdf/types": "^2.9.0",
"events": "^3.3.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"queue": "^6.0.1"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@react-pdf/stylesheet": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@react-pdf/stylesheet/-/stylesheet-6.1.0.tgz",
"integrity": "sha512-BGZ2sYNUp38VJUegjva/jsri3iiRGnVNjWI+G9dTwAvLNOmwFvSJzqaCsEnqQ/DW5mrTBk/577FhDY7pv6AidA==",
"license": "MIT",
"dependencies": {
"@react-pdf/fns": "3.1.2",
"@react-pdf/types": "^2.9.0",
"color-string": "^1.9.1",
"hsl-to-hex": "^1.0.0",
"media-engine": "^1.0.3",
"postcss-value-parser": "^4.1.0"
}
},
"node_modules/@react-pdf/textkit": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@react-pdf/textkit/-/textkit-6.0.0.tgz",
"integrity": "sha512-fDt19KWaJRK/n2AaFoVm31hgGmpygmTV7LsHGJNGZkgzXcFyLsx+XUl63DTDPH3iqxj3xUX128t104GtOz8tTw==",
"license": "MIT",
"dependencies": {
"@react-pdf/fns": "3.1.2",
"bidi-js": "^1.0.2",
"hyphen": "^1.6.4",
"unicode-properties": "^1.4.1"
}
},
"node_modules/@react-pdf/types": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/@react-pdf/types/-/types-2.9.0.tgz",
"integrity": "sha512-ckj80vZLlvl9oYrQ4tovEaqKWP3O06Eb1D48/jQWbdwz1Yh7Y9v1cEmwlP8ET+a1Whp8xfdM0xduMexkuPANCQ==",
"license": "MIT",
"dependencies": {
"@react-pdf/font": "^4.0.2",
"@react-pdf/primitives": "^4.1.1",
"@react-pdf/stylesheet": "^6.1.0"
}
},
"node_modules/@swc/counter": { "node_modules/@swc/counter": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@ -2039,6 +2214,12 @@
"node": ">=6.5" "node": ">=6.5"
} }
}, },
"node_modules/abs-svg-path": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz",
"integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==",
"license": "MIT"
},
"node_modules/agent-base": { "node_modules/agent-base": {
"version": "7.1.3", "version": "7.1.3",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
@ -2115,6 +2296,15 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/bidi-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
"integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
"license": "MIT",
"dependencies": {
"require-from-string": "^2.0.2"
}
},
"node_modules/bl": { "node_modules/bl": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-6.1.0.tgz", "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.0.tgz",
@ -2140,6 +2330,24 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/brotli": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz",
"integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==",
"license": "MIT",
"dependencies": {
"base64-js": "^1.1.2"
}
},
"node_modules/browserify-zlib": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
"integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
"license": "MIT",
"dependencies": {
"pako": "~1.0.5"
}
},
"node_modules/btoa": { "node_modules/btoa": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
@ -2289,6 +2497,15 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"license": "MIT",
"engines": {
"node": ">=0.8"
}
},
"node_modules/color": { "node_modules/color": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@ -2320,15 +2537,13 @@
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT", "license": "MIT"
"optional": true
}, },
"node_modules/color-string": { "node_modules/color-string": {
"version": "1.9.1", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"color-name": "^1.0.0", "color-name": "^1.0.0",
"simple-swizzle": "^0.2.2" "simple-swizzle": "^0.2.2"
@ -2512,6 +2727,12 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/dfa": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz",
"integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==",
"license": "MIT"
},
"node_modules/dompurify": { "node_modules/dompurify": {
"version": "3.2.5", "version": "3.2.5",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz",
@ -2545,6 +2766,12 @@
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
} }
}, },
"node_modules/emoji-regex": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
"license": "MIT"
},
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.18.1", "version": "5.18.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
@ -2701,6 +2928,12 @@
"node": ">=0.8.x" "node": ">=0.8.x"
} }
}, },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/fflate": { "node_modules/fflate": {
"version": "0.8.2", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
@ -2740,6 +2973,23 @@
} }
} }
}, },
"node_modules/fontkit": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz",
"integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==",
"license": "MIT",
"dependencies": {
"@swc/helpers": "^0.5.12",
"brotli": "^1.3.2",
"clone": "^2.1.2",
"dfa": "^1.2.0",
"fast-deep-equal": "^3.1.3",
"restructure": "^3.0.0",
"tiny-inflate": "^1.0.3",
"unicode-properties": "^1.4.0",
"unicode-trie": "^2.0.0"
}
},
"node_modules/form-data": { "node_modules/form-data": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
@ -2883,6 +3133,21 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/hsl-to-hex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/hsl-to-hex/-/hsl-to-hex-1.0.0.tgz",
"integrity": "sha512-K6GVpucS5wFf44X0h2bLVRDsycgJmf9FF2elg+CrqD8GcFU8c6vYhgXn8NjUkFCwj+xDFb70qgLbTUm6sxwPmA==",
"license": "MIT",
"dependencies": {
"hsl-to-rgb-for-reals": "^1.1.0"
}
},
"node_modules/hsl-to-rgb-for-reals": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/hsl-to-rgb-for-reals/-/hsl-to-rgb-for-reals-1.1.1.tgz",
"integrity": "sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==",
"license": "ISC"
},
"node_modules/html2canvas": { "node_modules/html2canvas": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
@ -2922,6 +3187,12 @@
"node": ">= 14" "node": ">= 14"
} }
}, },
"node_modules/hyphen": {
"version": "1.10.6",
"resolved": "https://registry.npmjs.org/hyphen/-/hyphen-1.10.6.tgz",
"integrity": "sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw==",
"license": "ISC"
},
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@ -2994,8 +3265,7 @@
"version": "0.3.2", "version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
"license": "MIT", "license": "MIT"
"optional": true
}, },
"node_modules/is-docker": { "node_modules/is-docker": {
"version": "3.0.0", "version": "3.0.0",
@ -3069,6 +3339,12 @@
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/is-url": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
"integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==",
"license": "MIT"
},
"node_modules/is-wsl": { "node_modules/is-wsl": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
@ -3090,6 +3366,15 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/jay-peg": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/jay-peg/-/jay-peg-1.1.1.tgz",
"integrity": "sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww==",
"license": "MIT",
"dependencies": {
"restructure": "^3.0.0"
}
},
"node_modules/jiti": { "node_modules/jiti": {
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
@ -3106,6 +3391,12 @@
"integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==", "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
"node_modules/jsonwebtoken": { "node_modules/jsonwebtoken": {
"version": "9.0.2", "version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
@ -3427,6 +3718,25 @@
"url": "https://opencollective.com/parcel" "url": "https://opencollective.com/parcel"
} }
}, },
"node_modules/linebreak": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz",
"integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==",
"license": "MIT",
"dependencies": {
"base64-js": "0.0.8",
"unicode-trie": "^2.0.0"
}
},
"node_modules/linebreak/node_modules/base64-js": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
"integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/lodash.debounce": { "node_modules/lodash.debounce": {
"version": "4.0.8", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@ -3481,6 +3791,18 @@
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "7.18.3", "version": "7.18.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
@ -3520,6 +3842,12 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/media-engine": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz",
"integrity": "sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==",
"license": "MIT"
},
"node_modules/micromatch": { "node_modules/micromatch": {
"version": "4.0.8", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
@ -3735,6 +4063,24 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/normalize-svg-path": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz",
"integrity": "sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==",
"license": "MIT",
"dependencies": {
"svg-arc-to-cubic-bezier": "^3.0.0"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/open": { "node_modules/open": {
"version": "10.1.0", "version": "10.1.0",
"resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz",
@ -3753,6 +4099,18 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)"
},
"node_modules/parse-svg-path": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz",
"integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==",
"license": "MIT"
},
"node_modules/path-key": { "node_modules/path-key": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@ -3817,6 +4175,12 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"license": "MIT"
},
"node_modules/prisma": { "node_modules/prisma": {
"version": "6.7.0", "version": "6.7.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.7.0.tgz", "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.7.0.tgz",
@ -3855,12 +4219,32 @@
"node": ">= 0.6.0" "node": ">= 0.6.0"
} }
}, },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"node_modules/proxy-from-env": { "node_modules/proxy-from-env": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
"license": "MIT",
"dependencies": {
"inherits": "~2.0.3"
}
},
"node_modules/raf": { "node_modules/raf": {
"version": "3.4.1", "version": "3.4.1",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
@ -3892,6 +4276,12 @@
"react": "^19.1.0" "react": "^19.1.0"
} }
}, },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/react-to-pdf": { "node_modules/react-to-pdf": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-to-pdf/-/react-to-pdf-2.0.0.tgz", "resolved": "https://registry.npmjs.org/react-to-pdf/-/react-to-pdf-2.0.0.tgz",
@ -3941,6 +4331,21 @@
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/restructure": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz",
"integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==",
"license": "MIT"
},
"node_modules/rfdc": { "node_modules/rfdc": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
@ -4090,7 +4495,6 @@
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"is-arrayish": "^0.3.1" "is-arrayish": "^0.3.1"
} }
@ -4199,6 +4603,12 @@
} }
} }
}, },
"node_modules/svg-arc-to-cubic-bezier": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz",
"integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==",
"license": "ISC"
},
"node_modules/svg-pathdata": { "node_modules/svg-pathdata": {
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
@ -4284,6 +4694,12 @@
"utrie": "^1.0.2" "utrie": "^1.0.2"
} }
}, },
"node_modules/tiny-inflate": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
"license": "MIT"
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -4329,6 +4745,32 @@
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/unicode-properties": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz",
"integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==",
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.0",
"unicode-trie": "^2.0.0"
}
},
"node_modules/unicode-trie": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
"integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
"license": "MIT",
"dependencies": {
"pako": "^0.2.5",
"tiny-inflate": "^1.0.0"
}
},
"node_modules/unicode-trie/node_modules/pako": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
"integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
"license": "MIT"
},
"node_modules/usehooks-ts": { "node_modules/usehooks-ts": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.1.tgz", "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.1.tgz",
@ -4344,6 +4786,12 @@
"react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" "react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc"
} }
}, },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/utrie": { "node_modules/utrie": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
@ -4362,6 +4810,26 @@
"uuid": "dist/bin/uuid" "uuid": "dist/bin/uuid"
} }
}, },
"node_modules/vite-compatible-readable-stream": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz",
"integrity": "sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/yoga-layout": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz",
"integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==",
"license": "MIT"
},
"node_modules/zustand": { "node_modules/zustand": {
"version": "5.0.3", "version": "5.0.3",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz",

View File

@ -16,6 +16,7 @@
}, },
"dependencies": { "dependencies": {
"@prisma/client": "^6.7.0", "@prisma/client": "^6.7.0",
"@react-pdf/renderer": "^4.3.0",
"@tanstack/react-query": "^5.71.0", "@tanstack/react-query": "^5.71.0",
"@tanstack/react-query-devtools": "^5.71.0", "@tanstack/react-query-devtools": "^5.71.0",
"@types/nodemailer": "^6.4.17", "@types/nodemailer": "^6.4.17",

View File

@ -0,0 +1,85 @@
import React from 'react'
import { NextRequest, NextResponse } from 'next/server'
import { pdf, Document } from '@react-pdf/renderer'
import { prisma } from '@/libs/prisma'
import { type Suitable } from '@/types/Suitable'
import SuitablePdf from '@/components/pdf/SuitablePdf'
export async function POST(request: NextRequest) {
// 파라미터 체크
const formData = await request.formData()
const ids = formData.get('ids') as string
const detailIds = formData.get('detailIds') as string
const fileTitle = formData.get('fileTitle') as string
if (ids === '' || detailIds === '' || fileTitle === '') {
return NextResponse.json({ error: '필수 파라미터가 누락되었습니다' }, { status: 400 })
}
try {
let query = `
SELECT
msm.id
, msm.product_name as productName
, ( SELECT bcl.code_jp FROM BC_COMM_L bcl
WHERE bcl.head_cd = (SELECT head_cd FROM BC_COMM_H bch WHERE head_id = 'MANU_FT_CD')
AND bcl.code = msm.manu_ft_cd ) AS manuFtCd
, ( SELECT bcl.code_jp FROM BC_COMM_L bcl
WHERE bcl.head_cd = (SELECT head_cd FROM BC_COMM_H bch WHERE head_id = 'ROOF_MT_CD')
AND bcl.code = msm.roof_mt_cd ) AS roofMtCd
, ( SELECT bcl.code_jp FROM BC_COMM_L bcl
WHERE bcl.head_cd = (SELECT head_cd FROM BC_COMM_H bch WHERE head_id = 'ROOF_SH_CD')
AND bcl.code = msm.roof_sh_cd ) AS roofShCd
, details.detail
FROM ms_suitable_main msm
LEFT JOIN (
SELECT
msd.main_id
, (
SELECT
msd_json.id
, ( SELECT bcl.code_jp FROM BC_COMM_L bcl
WHERE bcl.head_cd = (SELECT head_cd FROM BC_COMM_H bch WHERE head_id = 'TRESTLE_MFPC_CD')
AND bcl.code = msd_json.trestle_mfpc_cd ) AS trestleMfpcCd
, msd_json.trestle_manufacturer_product_name as trestleManufacturerProductName
, msd_json.memo
FROM ms_suitable_detail msd_json
WHERE msd.main_id = msd_json.main_id
AND msd_json.id IN (:detailIds)
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
msm.id IN (:mainIds)
ORDER BY msm.product_name;
`
// 검색 조건 설정
query = query.replaceAll(':mainIds', ids)
query = query.replaceAll(':detailIds', detailIds)
// 데이터 조회
const suitable: Suitable[] = await prisma.$queryRawUnsafe(query)
// pdf 생성
const content = React.createElement(Document, null, React.createElement(SuitablePdf, { data: suitable, fileTitle: fileTitle }))
const pdfBlob = await pdf(content).toBlob()
const fileName = `${fileTitle.replace(' ', '_')}.pdf`
const encodedFileName = encodeURIComponent(fileName)
return new Response(pdfBlob, {
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="suitable.pdf"; filename*=UTF-8''${encodedFileName}`,
},
})
} catch (error) {
console.error('❌ 데이터 조회 중 오류가 발생했습니다:', error)
return NextResponse.json({ error: '데이터 조회 중 오류가 발생했습니다' }, { status: 500 })
}
}

View File

@ -2,20 +2,96 @@ import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/libs/prisma' import { prisma } from '@/libs/prisma'
import { convertToSnakeCase } from '@/utils/common-utils' import { convertToSnakeCase } from '@/utils/common-utils'
interface Survey {
SRL_NO: string
SUBMISSION_STATUS: boolean
SUBMISSION_TARGET_ID: string | null
STORE_ID: string | null
CONSTRUCTION_POINT_ID: string | null
}
interface SessionParams {
role: string | null
storeId: string | null
builderNo: string | null
isLoggedIn: string | null
}
const checkT01Role = (survey: Survey): boolean => survey.SRL_NO !== '一時保存'
const checkAdminRole = (survey: Survey, storeId: string | null): boolean => {
if (!storeId) return false
if (survey.SUBMISSION_STATUS) {
return survey.SUBMISSION_TARGET_ID === storeId || survey.STORE_ID === storeId
}
return survey.STORE_ID === storeId
}
const checkAdminSubRole = (survey: Survey, storeId: string | null): boolean => {
if (!storeId) return false
if (survey.SUBMISSION_STATUS) {
return survey.SUBMISSION_TARGET_ID === storeId || (survey.STORE_ID === storeId && survey.CONSTRUCTION_POINT_ID === null)
}
return survey.STORE_ID === storeId && survey.CONSTRUCTION_POINT_ID === null
}
const checkPartnerOrBuilderRole = (survey: Survey, builderNo: string | null): boolean => {
if (!builderNo) return false
return survey.CONSTRUCTION_POINT_ID === builderNo
}
const checkRole = (survey: Survey, sessionParams: SessionParams): boolean => {
if (!survey || !sessionParams.role) return false
switch (sessionParams.role) {
case 'T01':
return checkT01Role(survey)
// T01 이외 1차점
case 'Admin':
return checkAdminRole(survey, sessionParams.storeId)
// 2차점
case 'Admin_Sub':
return checkAdminSubRole(survey, sessionParams.storeId)
// partner
case 'Partner':
// 2차점 시공권한 user
case 'Builder':
return checkPartnerOrBuilderRole(survey, sessionParams.builderNo)
default:
return false
}
}
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
try { try {
const { id } = await params const { id } = await params
const { searchParams } = new URL(request.url)
const sessionParams: SessionParams = {
role: searchParams.get('role'),
storeId: searchParams.get('storeId'),
builderNo: searchParams.get('builderNo'),
isLoggedIn: searchParams.get('isLoggedIn'),
}
// @ts-ignore // @ts-ignore
const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.findUnique({ const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.findFirst({
where: { ID: Number(id) }, where: {
ID: Number(id),
},
include: { include: {
DETAIL_INFO: true, DETAIL_INFO: true,
}, },
}) })
return NextResponse.json(survey) if (checkRole(survey, sessionParams)) {
} catch (error) { return NextResponse.json(survey)
} else {
return NextResponse.json({ error: '権限がありません。' }, { status: 403 })
}
} catch (error: any) {
console.error('Error fetching survey:', error) console.error('Error fetching survey:', error)
return NextResponse.json({ error: 'Failed to fetch survey' }, { status: 500 }) return NextResponse.json({ error: 'データの取得に失敗しました。' }, { status: 500 })
} }
} }
@ -115,20 +191,18 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
try { try {
const { id } = await params const { id } = await params
const body = await request.json() const body = await request.json()
// @ts-ignore
if (body.targetId) { const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({
// @ts-ignore where: { ID: Number(id) },
const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({ data: {
where: { ID: Number(id) }, SUBMISSION_STATUS: true,
data: { SUBMISSION_DATE: new Date(),
SUBMISSION_STATUS: true, SUBMISSION_TARGET_ID: body.targetId,
SUBMISSION_DATE: new Date(), SUBMISSION_TARGET_NM: body.targetNm,
SUBMISSION_TARGET_ID: body.targetId, UPT_DT: new Date(),
UPT_DT: new Date(), },
}, })
}) return NextResponse.json({ message: 'Survey confirmed successfully', data: survey })
return NextResponse.json({ message: 'Survey confirmed successfully', data: survey })
}
} 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 })

View File

@ -11,7 +11,7 @@ type SearchParams = {
sort?: string | null // 정렬 방식 sort?: string | null // 정렬 방식
offset?: string | null offset?: string | null
role?: string | null // 회원권한한 role?: string | null // 회원권한한
store?: string | null // 판매점ID storeId?: string | null // 판매점ID
builderNo?: string | null // 시공ID builderNo?: string | null // 시공ID
} }
@ -25,8 +25,10 @@ type WhereCondition = {
const SEARCH_OPTIONS = [ const SEARCH_OPTIONS = [
'BUILDING_NAME', // 건물명 'BUILDING_NAME', // 건물명
'REPRESENTATIVE', // 담당자 'REPRESENTATIVE', // 담당자
'STORE', // 판매점 'STORE', // 판매점명
'CONSTRUCTION_POINT', // 시공점 'STORE_ID', // 판매점ID
'CONSTRUCTION_POINT', // 시공점명
'CONSTRUCTION_POINT_ID', // 시공점ID
'CUSTOMER_NAME', // 고객명 'CUSTOMER_NAME', // 고객명
'POST_CODE', // 우편번호 'POST_CODE', // 우편번호
'ADDRESS', // 주소 'ADDRESS', // 주소
@ -75,11 +77,11 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => {
where.OR = [ where.OR = [
{ {
// 같은 판매점에서 작성한 제출/제출되지 않은 매물 // 같은 판매점에서 작성한 제출/제출되지 않은 매물
AND: [{ STORE_ID: { equals: params.store } }], AND: [{ STORE_ID: { equals: params.storeId } }],
}, },
{ {
// MUSUBI (시공권한 X) 가 ORDER 에 제출한 매물 // MUSUBI (시공권한 X) 가 ORDER 에 제출한 매물
AND: [{ SUBMISSION_TARGET_ID: { equals: params.store } }, { SUBMISSION_STATUS: { equals: true } }], AND: [{ SUBMISSION_TARGET_ID: { equals: params.storeId } }, { SUBMISSION_STATUS: { equals: true } }],
}, },
] ]
break break
@ -88,19 +90,14 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => {
where.OR = [ where.OR = [
{ {
// MUSUBI (시공권한 X) 같은 판매점에서 작성한 제출/제출되지 않은 매물 // MUSUBI (시공권한 X) 같은 판매점에서 작성한 제출/제출되지 않은 매물
AND: [ AND: [{ STORE_ID: { equals: params.storeId } }, { CONSTRUCTION_POINT_ID: { equals: params.builderNo } }],
{ STORE_ID: { equals: params.store } },
{
OR: [{ CONSTRUCTION_POINT: { equals: null } }, { CONSTRUCTION_POINT: { equals: '' } }],
},
],
}, },
{ {
// MUSUBI (시공권한 O) 가 MUSUBI 에 제출한 매물 + PARTNER 가 제출한 매물 // MUSUBI (시공권한 O) 가 MUSUBI 에 제출한 매물 + PARTNER 가 제출한 매물
AND: [ AND: [
{ SUBMISSION_TARGET_ID: { equals: params.store } }, { SUBMISSION_TARGET_ID: { equals: params.storeId } },
{ CONSTRUCTION_POINT: { not: null } }, { CONSTRUCTION_POINT_ID: { not: null } },
{ CONSTRUCTION_POINT: { not: '' } }, { CONSTRUCTION_POINT_ID: { not: '' } },
{ SUBMISSION_STATUS: { equals: true } }, { SUBMISSION_STATUS: { equals: true } },
], ],
}, },
@ -109,10 +106,9 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => {
case 'Builder': // MUSUBI (시공권한 O) case 'Builder': // MUSUBI (시공권한 O)
case 'Partner': // PARTNER case 'Partner': // PARTNER
// 시공점이 있고 STORE_ID가 시공ID와 같은 매물 // 시공ID 같은 매물
where.AND?.push({ where.AND?.push({
CONSTRUCTION_POINT: { not: null }, CONSTRUCTION_POINT_ID: { equals: params.builderNo },
STORE_ID: { equals: params.builderNo },
}) })
break break
@ -127,7 +123,7 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => {
}, },
{ {
STORE_ID: { STORE_ID: {
equals: params.store, equals: params.storeId,
}, },
}, },
] ]
@ -154,7 +150,7 @@ export async function GET(request: Request) {
sort: searchParams.get('sort'), sort: searchParams.get('sort'),
offset: searchParams.get('offset'), offset: searchParams.get('offset'),
role: searchParams.get('role'), role: searchParams.get('role'),
store: searchParams.get('store'), //storeId storeId: searchParams.get('storeId'), //storeId
builderNo: searchParams.get('builderNo'), builderNo: searchParams.get('builderNo'),
} }

Binary file not shown.

View File

@ -82,6 +82,7 @@ export default function SuitableDownloadPdf() {
<p></p> <p></p>
<p></p> <p></p>
</div> </div>
<div className="pdf-intro-foot-date">{createTime}</div>
</div> </div>
<div className="pdf-table-content"> <div className="pdf-table-content">
<div className="pdf-table-grid-wrap"> <div className="pdf-table-grid-wrap">
@ -123,6 +124,7 @@ export default function SuitableDownloadPdf() {
</div> </div>
))} ))}
</div> </div>
<div className="pdf-intro-foot-date">{createTime}</div>
</div> </div>
</div> </div>
</> </>

View File

@ -0,0 +1,124 @@
import fs from 'fs'
import path from 'path'
import { Font, Page, Text, View, StyleSheet } from '@react-pdf/renderer'
import type { Suitable, SuitableDetail } from '@/types/Suitable'
Font.register({
family: 'NotoSansJP',
src: `data:font/ttf;base64,${fs.readFileSync(path.resolve(process.cwd(), 'src/components/pdf/NotoSansJP-Regular.ttf')).toString('base64')}`,
})
const styles = StyleSheet.create({
page: {
padding: 30,
fontSize: 10,
fontFamily: 'NotoSansJP',
},
text: {
fontFamily: 'NotoSansJP',
},
table: {
display: 'flex',
width: 'auto',
borderStyle: 'solid',
borderWidth: 1,
borderColor: '#000',
},
tableRow: {
flexDirection: 'row',
},
tableColHeader: {
width: '25%',
borderStyle: 'solid',
borderWidth: 1,
backgroundColor: '#f0f0f0',
padding: 2,
},
tableCol: {
width: '25%',
borderStyle: 'solid',
borderWidth: 1,
padding: 2,
},
footer: {
position: 'absolute',
bottom: 30,
left: 30,
right: 30,
},
footerDate: {
fontSize: 10,
textAlign: 'right',
},
})
const formatDateString = () => {
const now = new Date()
const year = now.getFullYear()
const month = now.getMonth() + 1
const day = now.getDate()
const hours = now.getHours()
const minutes = now.getMinutes()
return `${year}${month}${day}${hours}:${minutes.toString().padStart(2, '0')}`
}
export default function SuitablePdf({ data, fileTitle }: { data: Suitable[]; fileTitle: string }) {
const createTime = formatDateString()
return (
<Page size="A4" orientation="landscape" style={styles.page}>
{/* Intro Section */}
<View>
<Text style={[styles.text]}></Text>
<Text style={[styles.text]}>{fileTitle}</Text>
<Text style={[styles.text]}>{createTime}</Text>
</View>
<View>
<Text style={[styles.text]}>使</Text>
<Text style={[styles.text]}></Text>
<Text style={[styles.text]}>
</Text>
<Text style={[styles.text]}></Text>
</View>
<View>
{/* Cards Section */}
{data?.map((item: Suitable) => (
<View key={item.id}>
<Text style={styles.text}>{item.productName}</Text>
<Text style={styles.text}>{item.manuFtCd}</Text>
<Text style={styles.text}>{item.roofMtCd}</Text>
<View style={styles.table}>
{/* Table Header */}
<View style={styles.tableRow}>
<Text style={[styles.tableColHeader, styles.text]}></Text>
<Text style={[styles.tableColHeader, styles.text]}></Text>
<Text style={[styles.tableColHeader, styles.text]}></Text>
<Text style={[styles.tableColHeader, styles.text]}></Text>
</View>
{/* Table Body */}
{JSON.parse(item.detail)?.map((subItem: SuitableDetail) => (
<View key={subItem.id} style={styles.tableRow}>
<Text style={[styles.tableCol, styles.text]}>{item.roofShCd}</Text>
<Text style={[styles.tableCol, styles.text]}>{subItem.trestleMfpcCd}</Text>
<Text style={[styles.tableCol, styles.text]}>{subItem.trestleManufacturerProductName}</Text>
<Text style={[styles.tableCol, styles.text]}>{subItem.memo}</Text>
</View>
))}
</View>
</View>
))}
</View>
{/* Footer */}
<View style={styles.footer} fixed>
<Text style={[styles.footerDate, styles.text]}>{createTime}</Text>
</View>
</Page>
)
}

View File

@ -10,7 +10,6 @@ import { useSpinnerStore } from '@/store/spinnerStore'
export default function SurveySaleDownloadPdf() { export default function SurveySaleDownloadPdf() {
const params = useParams() const params = useParams()
const id = params.id const id = params.id
const router = useRouter()
const { surveyDetail, isLoadingSurveyDetail } = useSurvey(Number(id)) const { surveyDetail, isLoadingSurveyDetail } = useSurvey(Number(id))
const { setIsShow } = useSpinnerStore() const { setIsShow } = useSpinnerStore()
@ -19,13 +18,13 @@ export default function SurveySaleDownloadPdf() {
const isGeneratedRef = useRef(false) const isGeneratedRef = useRef(false)
useEffect(() => { useEffect(() => {
setIsShow(true)
if (isLoadingSurveyDetail || !surveyDetail || isGeneratedRef.current) return if (isLoadingSurveyDetail || !surveyDetail || isGeneratedRef.current) return
isGeneratedRef.current = true isGeneratedRef.current = true
handleDownPdf() handleDownPdf()
}, [surveyDetail?.id, isLoadingSurveyDetail]) }, [surveyDetail?.id, isLoadingSurveyDetail])
const handleDownPdf = () => { const handleDownPdf = () => {
setIsShow(true)
const options = { const options = {
method: 'open' as const, method: 'open' as const,
resolution: Resolution.HIGH, resolution: Resolution.HIGH,
@ -50,12 +49,9 @@ export default function SurveySaleDownloadPdf() {
generatePDF(targetRef, options).then(() => { generatePDF(targetRef, options).then(() => {
setIsShow(false) setIsShow(false)
router.push(`/survey-sale/${id}`) alert('PDFの生成が完了しました。 ポップアップウィンドウからダウンロードしてください。')
}) })
} }
const supplementList = supplementaryFacilities
.filter((facility) => surveyDetail?.detailInfo?.supplementaryFacilities?.includes(facility.id.toString()))
.map((facility) => facility.name)
return ( return (
<> <>
@ -249,11 +245,15 @@ export default function SurveySaleDownloadPdf() {
boxSizing: 'border-box', boxSizing: 'border-box',
}} }}
> >
{supplementList === null && surveyDetail?.detailInfo?.supplementaryFacilitiesEtc === null {surveyDetail?.detailInfo?.supplementaryFacilities
? '-' ? supplementaryFacilities
.filter((facility) => surveyDetail?.detailInfo?.supplementaryFacilities?.includes(facility.id.toString()))
.map((facility) => facility.name)
.join(', ') +
(surveyDetail?.detailInfo?.supplementaryFacilitiesEtc ? `, ${surveyDetail?.detailInfo?.supplementaryFacilitiesEtc}` : '')
: surveyDetail?.detailInfo?.supplementaryFacilitiesEtc : surveyDetail?.detailInfo?.supplementaryFacilitiesEtc
? `${supplementList.join(', ')}, ${surveyDetail?.detailInfo?.supplementaryFacilitiesEtc}` ? `${surveyDetail?.detailInfo?.supplementaryFacilitiesEtc}`
: supplementList.join(', ')} : '-'}
</td> </td>
</tr> </tr>
<tr> <tr>
@ -330,7 +330,11 @@ export default function SurveySaleDownloadPdf() {
boxSizing: 'border-box', boxSizing: 'border-box',
}} }}
> >
{surveyDetail?.detailInfo?.constructionYear === '1' ? '新築' : `既築 (${surveyDetail?.detailInfo?.constructionYear}年)`} {surveyDetail?.detailInfo?.constructionYear === '1'
? '新築'
: surveyDetail?.detailInfo?.constructionYearEtc
? `既築 (${surveyDetail?.detailInfo?.constructionYear}年)`
: '-'}
</td> </td>
<th <th
style={{ style={{

View File

@ -2,10 +2,12 @@
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { usePopupController } from '@/store/popupController' import { usePopupController } from '@/store/popupController'
import { useSuitable } from '@/hooks/useSuitable'
export default function SuitableDetailPopupButton() { export default function SuitableDetailPopupButton() {
const popupController = usePopupController()
const router = useRouter() const router = useRouter()
const popupController = usePopupController()
const { downloadSuitablePdf } = useSuitable()
const handleClosePopup = () => { const handleClosePopup = () => {
popupController.setSuitableDetailPopup(false) popupController.setSuitableDetailPopup(false)
@ -24,7 +26,7 @@ export default function SuitableDetailPopupButton() {
</button> </button>
</div> </div>
<div className="btn-bx"> <div className="btn-bx">
<button className="btn-frame red icon" onClick={() => handleRedirectPage('/pdf/suitable')}> <button className="btn-frame red icon" onClick={downloadSuitablePdf}>
<i className="btn-arr"></i> <i className="btn-arr"></i>
</button> </button>
</div> </div>

View File

@ -2,7 +2,7 @@ import Image from 'next/image'
import { usePopupController } from '@/store/popupController' import { usePopupController } from '@/store/popupController'
import { useParams } from 'next/navigation' import { useParams } from 'next/navigation'
import { useSurvey } from '@/hooks/useSurvey' import { useSurvey } from '@/hooks/useSurvey'
import { useEffect, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { useSessionStore } from '@/store/session' import { useSessionStore } from '@/store/session'
import { useCommCode } from '@/hooks/useCommCode' import { useCommCode } from '@/hooks/useCommCode'
import { CommCode } from '@/types/CommCode' import { CommCode } from '@/types/CommCode'
@ -11,12 +11,13 @@ import { useSpinnerStore } from '@/store/spinnerStore'
interface SubmitFormData { interface SubmitFormData {
saleBase: string | null saleBase: string | null
store: string targetId: string | null
targetNm: string | null
sender: string sender: string
receiver: string[] | string receiver: string[] | string
reference: string | null reference: string | null
title: string title: string
contents: string contents: string | null
} }
interface FormField { interface FormField {
@ -32,38 +33,47 @@ export default function SurveySaleSubmitPopup() {
const routeId = params.id const routeId = params.id
const { setIsShow } = useSpinnerStore() const { setIsShow } = useSpinnerStore()
const [commCodeList, setCommCodeList] = useState<CommCode[]>([])
const { getCommCode } = useCommCode() const { getCommCode } = useCommCode()
const { surveyDetail } = useSurvey(Number(routeId))
const [submitData, setSubmitData] = useState<SubmitFormData>({
saleBase: null,
targetId: null,
targetNm: null,
sender: '',
receiver: [],
reference: null,
title: '',
contents: '',
})
const [commCodeList, setCommCodeList] = useState<CommCode[]>([])
useEffect(() => { useEffect(() => {
if (session?.isLoggedIn && session?.role === 'Admin') { if (!session?.isLoggedIn || !surveyDetail?.id) return
if (session?.role === 'Admin') {
getCommCode('SALES_OFFICE_CD').then((codes) => { getCommCode('SALES_OFFICE_CD').then((codes) => {
setCommCodeList(codes) setCommCodeList(codes)
}) })
} }
}, [session]) setSubmitData({
...submitData,
targetId: session?.role === 'Builder' ? surveyDetail?.storeId ?? null : null,
targetNm: session?.role === 'Builder' ? surveyDetail?.store ?? null : null,
sender: session?.email ?? '',
title: '[HANASYS現地調査] 調査物件が提出. (' + surveyDetail?.srlNo + ')',
})
}, [session, surveyDetail])
const FORM_FIELDS: FormField[] = [ const FORM_FIELDS: FormField[] = [
{ id: 'saleBase', name: '提出地点選択', required: session?.role === 'Admin' },
{ id: 'store', name: '提出販売店', required: true },
{ id: 'sender', name: '発送者', required: true }, { id: 'sender', name: '発送者', required: true },
{ id: 'saleBase', name: '提出地点選択', required: session?.role === 'Admin' },
{ id: 'targetNm', name: '提出販売店', required: session?.role !== 'Admin' },
{ id: 'receiver', name: '受信者', required: true }, { id: 'receiver', name: '受信者', required: true },
{ id: 'reference', name: '参考', required: false }, { id: 'reference', name: '参考', required: false },
{ id: 'title', name: 'タイトル', required: true }, { id: 'title', name: 'タイトル', required: true },
{ id: 'contents', name: '内容', required: true }, { id: 'contents', name: '内容', required: false },
] ]
const [submitData, setSubmitData] = useState<SubmitFormData>({
saleBase: null,
store: '',
sender: session?.email ?? '',
receiver: [],
reference: null,
title: '[HANASYS現地調査] 調査物件が提出.',
contents: '',
})
const { submitSurvey, isSubmittingSurvey } = useSurvey(Number(routeId)) const { submitSurvey, isSubmittingSurvey } = useSurvey(Number(routeId))
const handleInputChange = (field: keyof SubmitFormData, value: string) => { const handleInputChange = (field: keyof SubmitFormData, value: string) => {
@ -74,7 +84,7 @@ export default function SurveySaleSubmitPopup() {
const requiredFields = FORM_FIELDS.filter((field) => field.required) const requiredFields = FORM_FIELDS.filter((field) => field.required)
for (const field of requiredFields) { for (const field of requiredFields) {
if (data[field.id]?.length === 0) { if (data[field.id] === '' || data[field.id] === null || data[field.id]?.length === 0) {
alert(`${field.name}は必須入力項目です。`) alert(`${field.name}は必須入力項目です。`)
const element = document.getElementById(field.id) const element = document.getElementById(field.id)
if (element) { if (element) {
@ -86,32 +96,38 @@ export default function SurveySaleSubmitPopup() {
return true return true
} }
// TODO: Admin_Sub 계정 매핑된 submit target id 추가!!!! && 메일 테스트트
const handleSubmit = () => { const handleSubmit = () => {
if (validateData(submitData)) { if (validateData(submitData)) {
window.neoConfirm('送信しますか? 送信後は変更・修正することはできません。', () => { window.neoConfirm('送信しますか? 送信後は変更・修正することはできません。', () => {
setIsShow(true) setIsShow(true)
submitSurvey({ targetId: submitData.store })
sendEmail({ sendEmail({
to: submitData.receiver, to: submitData.receiver,
subject: submitData.title, subject: submitData.title,
content: submitData.contents, content: contentsRef.current?.innerHTML ?? '',
}) })
.then(() => { .then(() => {
if (!isSubmittingSurvey) { if (!isSubmittingSurvey) {
popupController.setSurveySaleSubmitPopup(false) alert('提出が完了しました。')
} // submitSurvey({ targetId: submitData.targetId, targetNm: submitData.targetNm })
}) popupController.setSurveySaleSubmitPopup(false)
.catch((error) => { }
console.error('Error sending email:', error) })
alert('メール送信に失敗しました。') .catch((error) => {
console.error('Error sending email:', error)
alert('メール送信に失敗しました。 再度送信してください。')
}) })
.finally(() => { .finally(() => {
submitSurvey({ targetId: submitData.targetId, targetNm: submitData.targetNm })
setIsShow(false) setIsShow(false)
popupController.setSurveySaleSubmitPopup(false)
}) })
}) })
} }
} }
const contentsRef = useRef<HTMLDivElement>(null)
const handleClose = () => { const handleClose = () => {
popupController.setSurveySaleSubmitPopup(false) popupController.setSurveySaleSubmitPopup(false)
} }
@ -122,6 +138,9 @@ export default function SurveySaleSubmitPopup() {
if (field.id === 'saleBase' && session?.role !== 'Admin') { if (field.id === 'saleBase' && session?.role !== 'Admin') {
return null return null
} }
if (field.id === 'targetNm' && session?.role === 'Admin') {
return null
}
return ( return (
<div className="data-input-form-bx" key={field.id}> <div className="data-input-form-bx" key={field.id}>
@ -130,12 +149,38 @@ export default function SurveySaleSubmitPopup() {
</div> </div>
<div className="data-input"> <div className="data-input">
{field.id === 'contents' ? ( {field.id === 'contents' ? (
<textarea <div className="submit-content" id={field.id}>
className="textarea-form" <div ref={contentsRef}>
id={field.id} <p style={{ fontSize: '13px', fontWeight: '400', color: '#2e3a59', marginBottom: '15px' }}>
value={submitData[field.id] ?? ''} HANASYS現地調査アプリを使用した現地調査結果が送信されました
onChange={(e) => handleInputChange(field.id, e.target.value)} </p>
/> <p style={{ fontSize: '13px', fontWeight: '400', color: '#2e3a59', marginBottom: '3px' }}>
- <span style={{ color: '#417DDC' }}>{surveyDetail?.representative}</span>
</p>
<p style={{ fontSize: '13px', fontWeight: '400', color: '#2e3a59', marginBottom: '3px' }}>
-
<span style={{ color: '#417DDC' }}>
{surveyDetail?.store} ({surveyDetail?.storeId})
</span>
</p>
<p style={{ fontSize: '13px', fontWeight: '400', color: '#2e3a59', marginBottom: '15px' }}>
-:
<span style={{ color: '#417DDC' }}>{surveyDetail?.constructionPoint}</span>
</p>
<p>
<a
style={{ fontSize: '13px', fontWeight: '400', color: '#1259CB', marginBottom: '5px', textDecoration: 'underline' }}
href={`${process.env.NEXT_PUBLIC_API_URL}/pdf/survey-sale/${surveyDetail?.id}`}
target="_blank"
>
調PDFダウンロード
</a>
</p>
<p style={{ fontSize: '13px', fontWeight: '400', color: '#2e3a59' }}>
調PDFをダウンロードできます
</p>
</div>
</div>
) : ( ) : (
<> <>
{field.id === 'saleBase' && session?.role === 'Admin' ? ( {field.id === 'saleBase' && session?.role === 'Admin' ? (

View File

@ -107,6 +107,13 @@ export default function ZipCodePopup() {
<td>{item.address3}</td> <td>{item.address3}</td>
</tr> </tr>
))} ))}
{addressInfo?.length === 0 && (
<tr>
<td colSpan={3} className="al-c">
<div className="nodata"> .</div>
</td>
</tr>
)}
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -1,14 +1,12 @@
'use client' 'use client'
import { useRouter } from 'next/navigation'
import { usePopupController } from '@/store/popupController' import { usePopupController } from '@/store/popupController'
import { useSuitable } from '@/hooks/useSuitable' import { useSuitable } from '@/hooks/useSuitable'
import { useSuitableStore } from '@/store/useSuitableStore' import { useSuitableStore } from '@/store/useSuitableStore'
export default function SuitableButton() { export default function SuitableButton() {
const popupController = usePopupController() const popupController = usePopupController()
const router = useRouter() const { getSuitableIds, clearSuitableStore, downloadSuitablePdf } = useSuitable()
const { getSuitableIds, clearSuitableStore } = useSuitable()
const { selectedItems, addAllSelectedItem } = useSuitableStore() const { selectedItems, addAllSelectedItem } = useSuitableStore()
const handleSelectAll = async () => { const handleSelectAll = async () => {
@ -28,7 +26,7 @@ export default function SuitableButton() {
alert('屋根材を選択してください。') alert('屋根材を選択してください。')
return return
} }
router.push('/pdf/suitable') downloadSuitablePdf()
} }
return ( return (

View File

@ -4,46 +4,38 @@ import { useEffect, useState } from 'react'
import { useSurveySaleTabState } from '@/store/surveySaleTabState' import { useSurveySaleTabState } from '@/store/surveySaleTabState'
import type { SurveyBasicRequest } from '@/types/Survey' import type { SurveyBasicRequest } from '@/types/Survey'
import type { Mode } from 'fs' import type { Mode } from 'fs'
import { useSessionStore } from '@/store/session'
import { usePopupController } from '@/store/popupController' import { usePopupController } from '@/store/popupController'
import { useAddressStore } from '@/store/addressStore' import { useAddressStore } from '@/store/addressStore'
import { SessionData } from '@/types/Auth'
export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBasicInfo: (basicInfo: SurveyBasicRequest) => void; mode: Mode }) { interface BasicFormProps {
const { basicInfo, setBasicInfo, mode } = props basicInfo: SurveyBasicRequest
setBasicInfo: (basicInfo: SurveyBasicRequest) => void
mode: Mode
session: SessionData
}
export default function BasicForm({ basicInfo, setBasicInfo, mode, session }: BasicFormProps) {
const { setBasicInfoSelected } = useSurveySaleTabState() const { setBasicInfoSelected } = useSurveySaleTabState()
const [isFlip, setIsFlip] = useState<boolean>(true) const [isFlip, setIsFlip] = useState<boolean>(true)
const { session } = useSessionStore()
const { addressData } = useAddressStore() const { addressData } = useAddressStore()
const popupController = usePopupController()
useEffect(() => { useEffect(() => {
setBasicInfoSelected() setBasicInfoSelected()
}, []) }, [])
// 시공권한 user(Builder), Partner 계정은 조사매물 등록 할 때 STORE_ID에 시공점ID가 들어감 // 주소 데이터가 변경될 때만 업데이트
// 권한 별 목록 필터링 시 시공권한 user(Builder), Partner는 시공점ID가 같은 것들만 조회
useEffect(() => { useEffect(() => {
if (session?.isLoggedIn) { if (!addressData) return
setBasicInfo({
...basicInfo,
representative: session.userNm ?? '',
representativeId: session.userId ?? null,
store: session.role === 'Partner' ? null : session.storeNm ?? null,
storeId: session.role === 'Partner' || session.role === 'Builder' ? session.builderNo : session.storeId ?? 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() setBasicInfo({
...basicInfo,
postCode: addressData.post_code,
address: addressData.address,
addressDetail: addressData.address_detail,
})
}, [addressData])
return ( return (
<> <>
@ -67,29 +59,15 @@ export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBas
onChange={(e) => setBasicInfo({ ...basicInfo, representative: e.target.value })} onChange={(e) => setBasicInfo({ ...basicInfo, representative: e.target.value })}
/> />
</div> </div>
{(session?.role === 'Builder' || session?.role?.includes('Admin')) && ( {mode === 'READ' || session?.role === 'Builder' ? (
<div className="data-input-form-bx"> <>
<div className="data-input-form-tit"></div> {storeInput(basicInfo, setBasicInfo, mode)}
<input {builderInput(basicInfo, setBasicInfo, mode)}
type="text" </>
className="input-frame" ) : session?.role === 'Partner' ? (
readOnly <>{builderInput(basicInfo, setBasicInfo, mode)}</>
value={basicInfo?.store ?? ''} ) : (
onChange={(e) => setBasicInfo({ ...basicInfo, store: e.target.value })} <>{storeInput(basicInfo, setBasicInfo, mode)}</>
/>
</div>
)}
{(session?.role === 'Builder' || session?.role === 'Partner') && (
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<input
type="text"
className="input-frame"
readOnly
value={basicInfo?.constructionPoint ?? ''}
onChange={(e) => setBasicInfo({ ...basicInfo, constructionPoint: e.target.value })}
/>
</div>
)} )}
</div> </div>
</div> </div>
@ -169,3 +147,33 @@ export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBas
</> </>
) )
} }
const storeInput = (basicInfo: SurveyBasicRequest, setBasicInfo: (basicInfo: SurveyBasicRequest) => void, mode: Mode) => {
return (
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<input
type="text"
className="input-frame"
readOnly
value={basicInfo?.store ?? ''}
onChange={(e) => setBasicInfo({ ...basicInfo, store: e.target.value })}
/>
</div>
)
}
const builderInput = (basicInfo: SurveyBasicRequest, setBasicInfo: (basicInfo: SurveyBasicRequest) => void, mode: Mode) => {
return (
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<input
type="text"
className="input-frame"
readOnly
value={basicInfo?.constructionPoint ?? ''}
onChange={(e) => setBasicInfo({ ...basicInfo, constructionPoint: e.target.value })}
/>
</div>
)
}

View File

@ -7,76 +7,88 @@ import { useParams, useRouter, useSearchParams } from 'next/navigation'
import { requiredFields, useSurvey } from '@/hooks/useSurvey' import { requiredFields, useSurvey } from '@/hooks/useSurvey'
import { usePopupController } from '@/store/popupController' import { usePopupController } from '@/store/popupController'
export default function ButtonForm(props: { interface ButtonFormProps {
mode: Mode mode: Mode
setMode: (mode: Mode) => void setMode: (mode: Mode) => void
data: { basic: SurveyBasicRequest; roof: SurveyDetailRequest } data: {
}) { basic: SurveyBasicRequest
// 라우터 roof: SurveyDetailRequest
const router = useRouter() }
const { mode, setMode } = props }
const { session } = useSessionStore()
interface PermissionState {
isSubmiter: boolean
isWriter: boolean
isReceiver: boolean
}
interface SaveData extends SurveyBasicRequest {
detailInfo: SurveyDetailRequest
}
export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
const router = useRouter()
const { session } = useSessionStore()
const searchParams = useSearchParams() const searchParams = useSearchParams()
const idParam = searchParams.get('id') const idParam = searchParams.get('id')
const params = useParams() const params = useParams()
const routeId = params.id const routeId = params.id
const popupController = usePopupController() const popupController = usePopupController()
// ------------------------------------------------------------
const [saveData, setSaveData] = useState({ const [saveData, setSaveData] = useState<SaveData>({
...props.data.basic, ...data.basic,
detailInfo: props.data.roof, detailInfo: data.roof,
}) })
// --------------------------------------------------------------
// 권한
// 제출권한 ㅇ const [permissions, setPermissions] = useState<PermissionState>({
const [isSubmiter, setIsSubmiter] = useState(false) isSubmiter: false,
// 작성자 isWriter: false,
const [isWriter, setIsWriter] = useState(false) isReceiver: false,
const isSubmit = props.data.basic.submissionStatus })
useEffect(() => { const isSubmit = data.basic.submissionStatus
if (session?.isLoggedIn) {
switch (session?.role) {
// T01 제출권한 없음
case 'T01':
setIsSubmiter(false)
break
// 1차 판매점(Order) + 2차 판매점(Musubi) => 같은 판매점 제출권한
case 'Admin':
case 'Admin_Sub':
setIsSubmiter(session.storeNm === props.data.basic.store && session.builderNo === props.data.basic.constructionPoint)
break
// 시공권한 User(Musubi) + Partner => 같은 시공ID 제출권한
case 'Builder':
case 'Partner':
setIsSubmiter(session.builderNo === props.data.basic.constructionPoint)
break
default:
setIsSubmiter(false)
break
}
setIsWriter(session.userNm === props.data.basic.representative)
}
setSaveData({
...props.data.basic,
detailInfo: props.data.roof,
})
}, [session, props.data])
// ------------------------------------------------------------
// 저장/임시저장/수정
const id = Number(routeId) ? Number(routeId) : Number(idParam) const id = Number(routeId) ? Number(routeId) : Number(idParam)
const { deleteSurvey, updateSurvey, isDeletingSurvey, isUpdatingSurvey } = useSurvey(Number(id)) const { deleteSurvey, updateSurvey, isDeletingSurvey, isUpdatingSurvey } = useSurvey(id)
const { validateSurveyDetail, createSurvey, isCreatingSurvey } = useSurvey() const { validateSurveyDetail, createSurvey, isCreatingSurvey } = useSurvey()
const handleSave = (isTemporary: boolean, isSubmitProcess = false) => { useEffect(() => {
const emptyField = validateSurveyDetail(props.data.roof) if (!session?.isLoggedIn) return
const newPermissions = calculatePermissions(session, data.basic)
setPermissions(newPermissions)
setSaveData({
...data.basic,
detailInfo: data.roof,
})
}, [session, data])
const calculatePermissions = (session: any, basicData: SurveyBasicRequest): PermissionState => {
const isSubmiter = calculateSubmitPermission(session, basicData)
const isWriter = session.userNm === basicData.representative
const isReceiver = session?.storeId === basicData.submissionTargetId
return { isSubmiter, isWriter, isReceiver }
}
const calculateSubmitPermission = (session: any, basicData: SurveyBasicRequest): boolean => {
switch (session?.role) {
case 'T01':
return false
case 'Admin':
case 'Admin_Sub':
return session.storeNm === basicData.store && session.builderNo === basicData.constructionPointId
case 'Builder':
case 'Partner':
return session.builderNo === basicData.constructionPointId
default:
return false
}
}
const handleSave = (isTemporary: boolean, isSubmitProcess: boolean) => {
const emptyField = validateSurveyDetail(data.roof)
const hasEmptyField = emptyField?.trim() !== '' const hasEmptyField = emptyField?.trim() !== ''
if (isTemporary) { if (isTemporary) {
@ -89,53 +101,65 @@ export default function ButtonForm(props: {
const tempSaveProcess = async () => { const tempSaveProcess = async () => {
if (idParam) { if (idParam) {
await updateSurvey({ survey: saveData, isTemporary: true }) await updateSurvey({ survey: saveData, isTemporary: true })
router.push(`/survey-sale/${idParam}`) if (!isUpdatingSurvey) {
router.push(`/survey-sale/${idParam}`)
}
} else { } else {
const updatedData = { const updatedData = {
...saveData, ...saveData,
srlNo: '一時保存', srlNo: '一時保存',
} }
const id = await createSurvey(updatedData) const id = await createSurvey(updatedData)
router.push(`/survey-sale/${id}`) if (!isCreatingSurvey) {
router.push(`/survey-sale/${id}`)
}
} }
alert('一時保存されました。') alert('一時保存されました。')
} }
const focusInput = (field: keyof SurveyDetailInfo) => { const focusInput = (field: keyof SurveyDetailInfo) => {
const input = document.getElementById(field) const input = document.getElementById(field)
if (input) { input?.focus()
input.focus()
}
} }
const saveProcess = async (emptyField: string | null, isSubmitProcess?: boolean) => { const saveProcess = async (emptyField: string | null, isSubmitProcess?: boolean) => {
if (emptyField?.trim() === '') { if (emptyField?.trim() === '') {
if (idParam) { await handleSuccessfulSave(isSubmitProcess)
await updateSurvey({ survey: saveData, isTemporary: false, storeId: session.storeId ?? '' })
router.push(`/survey-sale/${idParam}`)
} else {
const id = await createSurvey(saveData)
router.push(`/survey-sale/${id}`)
}
if (isSubmitProcess) {
if (!isCreatingSurvey && !isUpdatingSurvey) {
popupController.setSurveySaleSubmitPopup(true)
}
} else {
alert('保存されました。')
}
} else { } else {
if (emptyField?.includes('Unit')) { handleFailedSave(emptyField)
alert('電気契約容量の単位を入力してください。')
focusInput(emptyField as keyof SurveyDetailInfo)
} else {
alert(requiredFields.find((field) => field.field === emptyField)?.name + ' 項目が空です。')
focusInput(emptyField as keyof SurveyDetailInfo)
}
} }
} }
// ------------------------------------------------------------
// 삭제/제출 const handleSuccessfulSave = async (isSubmitProcess?: boolean) => {
if (idParam) {
await updateSurvey({ survey: saveData, isTemporary: false, storeId: session.storeId ?? '' })
if (!isUpdatingSurvey) {
router.push(`/survey-sale/${idParam}`)
}
} else {
const id = await createSurvey(saveData)
if (!isCreatingSurvey) {
router.push(`/survey-sale/${id}`)
}
}
if (isSubmitProcess) {
if (!isCreatingSurvey && !isUpdatingSurvey) {
await popupController.setSurveySaleSubmitPopup(true)
}
} else {
alert('保存されました。')
}
}
const handleFailedSave = (emptyField: string | null) => {
if (emptyField?.includes('Unit')) {
alert('電気契約容量の単位を入力してください。')
} else {
alert(requiredFields.find((field) => field.field === emptyField)?.name + ' 項目が空です。')
}
focusInput(emptyField as keyof SurveyDetailInfo)
}
const handleDelete = async () => { const handleDelete = async () => {
if (routeId) { if (routeId) {
@ -150,10 +174,11 @@ export default function ButtonForm(props: {
} }
const handleSubmit = async () => { const handleSubmit = async () => {
if (props.data.basic.srlNo?.startsWith('一時保存') && Number(routeId)) { if (data.basic.srlNo?.startsWith('一時保存') && Number(routeId)) {
alert('一時保存されたデータは提出できません。') alert('一時保存されたデータは提出できません。')
return return
} }
if (Number(routeId)) { if (Number(routeId)) {
window.neoConfirm('提出しますか?', async () => { window.neoConfirm('提出しますか?', async () => {
popupController.setSurveySaleSubmitPopup(true) popupController.setSurveySaleSubmitPopup(true)
@ -165,17 +190,15 @@ export default function ButtonForm(props: {
} }
} }
// ------------------------------------------------------------ if (!session?.isLoggedIn) return null
if (mode === 'READ' && isSubmit && isSubmiter) { if (mode === 'READ' && isSubmit && permissions.isSubmiter) {
return ( return (
<> <div className="sale-form-btn-wrap">
<div className="sale-form-btn-wrap"> <div className="btn-flex-wrap">
<div className="btn-flex-wrap"> <ListButton />
<ListButton />
</div>
</div> </div>
</> </div>
) )
} }
@ -185,9 +208,11 @@ export default function ButtonForm(props: {
<div className="sale-form-btn-wrap"> <div className="sale-form-btn-wrap">
<div className="btn-flex-wrap"> <div className="btn-flex-wrap">
<ListButton /> <ListButton />
<EditButton setMode={setMode} id={id.toString()} mode={mode} /> {(permissions.isWriter || permissions.isSubmiter || (permissions.isReceiver && isSubmit)) && (
{(isWriter || !isSubmiter) && <DeleteButton handleDelete={handleDelete} />} <EditButton setMode={setMode} id={id.toString()} />
{!isSubmit && isSubmiter && <SubmitButton handleSubmit={handleSubmit} />} )}
{(permissions.isWriter || (permissions.isReceiver && isSubmit)) && <DeleteButton handleDelete={handleDelete} />}
{!isSubmit && permissions.isSubmiter && <SubmitButton handleSubmit={handleSubmit} />}
</div> </div>
</div> </div>
)} )}
@ -196,9 +221,9 @@ export default function ButtonForm(props: {
<div className="sale-form-btn-wrap"> <div className="sale-form-btn-wrap">
<div className="btn-flex-wrap"> <div className="btn-flex-wrap">
<ListButton /> <ListButton />
<TempButton setMode={setMode} handleSave={handleSave} /> <TempButton handleSave={() => handleSave(true, false)} />
<SaveButton handleSave={handleSave} /> <SaveButton handleSave={() => handleSave(false, false)} />
{session?.role !== 'T01' && <SubmitButton handleSubmit={handleSubmit} />} {session?.role === 'T01' || isSubmit ? null : <SubmitButton handleSubmit={handleSubmit} />}
</div> </div>
</div> </div>
)} )}
@ -206,12 +231,11 @@ export default function ButtonForm(props: {
) )
} }
// 목록 버튼 // Button Components
function ListButton() { const ListButton = () => {
const router = useRouter() const router = useRouter()
return ( return (
<div className="btn-bx"> <div className="btn-bx">
{/* 목록 */}
<button className="btn-frame n-blue icon" onClick={() => router.push('/survey-sale')}> <button className="btn-frame n-blue icon" onClick={() => router.push('/survey-sale')}>
<i className="btn-arr"></i> <i className="btn-arr"></i>
</button> </button>
@ -219,12 +243,10 @@ function ListButton() {
) )
} }
function EditButton(props: { setMode: (mode: Mode) => void; id: string; mode: Mode }) { const EditButton = ({ setMode, id }: { setMode: (mode: Mode) => void; id: string }) => {
const { setMode, id, mode } = props
const router = useRouter() const router = useRouter()
return ( return (
<div className="btn-bx"> <div className="btn-bx">
{/* 수정 */}
<button <button
className="btn-frame n-blue icon" className="btn-frame n-blue icon"
onClick={() => { onClick={() => {
@ -238,59 +260,34 @@ function EditButton(props: { setMode: (mode: Mode) => void; id: string; mode: Mo
) )
} }
function SubmitButton(props: { handleSubmit: () => void }) { const SubmitButton = ({ handleSubmit }: { handleSubmit: () => void }) => (
const { handleSubmit } = props <div className="btn-bx">
return ( <button className="btn-frame red icon" onClick={handleSubmit}>
<> <i className="btn-arr"></i>
<div className="btn-bx"> </button>
{/* 제출 */} </div>
<button className="btn-frame red icon" onClick={handleSubmit}> )
<i className="btn-arr"></i>
</button>
</div>
</>
)
}
function DeleteButton(props: { handleDelete: () => void }) { const DeleteButton = ({ handleDelete }: { handleDelete: () => void }) => (
const { handleDelete } = props <div className="btn-bx">
return ( <button className="btn-frame n-blue icon" onClick={handleDelete}>
<div className="btn-bx"> <i className="btn-arr"></i>
{/* 삭제 */} </button>
<button className="btn-frame n-blue icon" onClick={handleDelete}> </div>
<i className="btn-arr"></i> )
</button>
</div>
)
}
function SaveButton(props: { handleSave: (isTemporary: boolean) => void }) { const SaveButton = ({ handleSave }: { handleSave: () => void }) => (
const { handleSave } = props <div className="btn-bx">
return ( <button className="btn-frame n-blue icon" onClick={handleSave}>
<div className="btn-bx"> <i className="btn-arr"></i>
{/* 저장 */} </button>
<button className="btn-frame n-blue icon" onClick={() => handleSave(false)}> </div>
<i className="btn-arr"></i> )
</button>
</div>
)
}
function TempButton(props: { setMode: (mode: Mode) => void; handleSave: (isTemporary: boolean) => void }) { const TempButton = ({ handleSave }: { handleSave: () => void }) => (
const { setMode, handleSave } = props <div className="btn-bx">
const router = useRouter() <button className="btn-frame n-blue icon" onClick={handleSave}>
<i className="btn-arr"></i>
return ( </button>
<div className="btn-bx"> </div>
{/* 임시저장 */} )
<button
className="btn-frame n-blue icon"
onClick={() => {
handleSave(true)
}}
>
<i className="btn-arr"></i>
</button>
</div>
)
}

View File

@ -4,12 +4,15 @@ import { useSurvey } from '@/hooks/useSurvey'
import { useParams, useRouter } from 'next/navigation' import { useParams, useRouter } from 'next/navigation'
import { useEffect } from 'react' import { useEffect } from 'react'
import DetailForm from './DetailForm' import DetailForm from './DetailForm'
import { useSessionStore } from '@/store/session'
export default function DataTable() { export default function DataTable() {
const params = useParams() const params = useParams()
const id = params.id const id = params.id
const router = useRouter() const router = useRouter()
const { session } = useSessionStore()
useEffect(() => { useEffect(() => {
if (Number.isNaN(Number(id))) { if (Number.isNaN(Number(id))) {
alert('間違ったアプローチです。') alert('間違ったアプローチです。')
@ -20,7 +23,25 @@ export default function DataTable() {
const { surveyDetail, isLoadingSurveyDetail } = useSurvey(Number(id)) const { surveyDetail, isLoadingSurveyDetail } = useSurvey(Number(id))
if (isLoadingSurveyDetail) { if (isLoadingSurveyDetail) {
return <div>Loading...</div> return null
}
const submitStatus = () => {
const { submissionTargetNm, submissionTargetId } = surveyDetail ?? {}
if (!submissionTargetNm) {
return null
}
if (!submissionTargetId) {
return <div>{submissionTargetNm}</div>
}
return (
<div>
({submissionTargetNm} - {submissionTargetId})
</div>
)
} }
return ( return (
@ -56,9 +77,7 @@ export default function DataTable() {
{surveyDetail?.submissionStatus && surveyDetail?.submissionDate ? ( {surveyDetail?.submissionStatus && surveyDetail?.submissionDate ? (
<> <>
<div>{new Date(surveyDetail.submissionDate).toLocaleString()}</div> <div>{new Date(surveyDetail.submissionDate).toLocaleString()}</div>
<div> {submitStatus()}
({surveyDetail.store} - {surveyDetail.storeId})
</div>
</> </>
) : ( ) : (
'-' '-'

View File

@ -7,6 +7,7 @@ import BasicForm from './BasicForm'
import RoofForm from './RoofForm' import RoofForm from './RoofForm'
import { useParams, useSearchParams } from 'next/navigation' import { useParams, useSearchParams } from 'next/navigation'
import { useSurvey } from '@/hooks/useSurvey' import { useSurvey } from '@/hooks/useSurvey'
import { useSessionStore } from '@/store/session'
const roofInfoForm: SurveyDetailRequest = { const roofInfoForm: SurveyDetailRequest = {
contractCapacity: null, contractCapacity: null,
@ -52,6 +53,7 @@ const basicInfoForm: SurveyBasicRequest = {
store: null, store: null,
storeId: null, storeId: null,
constructionPoint: null, constructionPoint: null,
constructionPointId: null,
investigationDate: new Date().toLocaleDateString('en-CA'), investigationDate: new Date().toLocaleDateString('en-CA'),
buildingName: null, buildingName: null,
customerName: null, customerName: null,
@ -61,6 +63,7 @@ const basicInfoForm: SurveyBasicRequest = {
submissionStatus: false, submissionStatus: false,
submissionDate: null, submissionDate: null,
submissionTargetId: null, submissionTargetId: null,
submissionTargetNm: null,
srlNo: null, srlNo: null,
} }
@ -71,29 +74,54 @@ export default function DetailForm() {
const modeset = Number(routeId) ? 'READ' : idParam ? 'EDIT' : 'CREATE' const modeset = Number(routeId) ? 'READ' : idParam ? 'EDIT' : 'CREATE'
const id = Number(routeId) ? Number(routeId) : Number(idParam) const id = Number(routeId) ? Number(routeId) : Number(idParam)
const { surveyDetail, validateSurveyDetail } = useSurvey(Number(id)) const { surveyDetail, isLoadingSurveyDetail, validateSurveyDetail } = useSurvey(Number(id))
const { session } = useSessionStore()
const [mode, setMode] = useState<Mode>(modeset) const [mode, setMode] = useState<Mode>(modeset)
const [basicInfoData, setBasicInfoData] = useState<SurveyBasicRequest>(basicInfoForm) const [basicInfoData, setBasicInfoData] = useState<SurveyBasicRequest>(() => ({
...basicInfoForm,
representative: session?.userNm ?? '',
representativeId: session?.userId ?? null,
store: session?.storeNm ?? null,
storeId: session?.storeId ?? null,
constructionPoint: session?.builderNm ?? null,
constructionPointId: session?.builderNo ?? null,
}))
const [roofInfoData, setRoofInfoData] = useState<SurveyDetailRequest>(roofInfoForm) const [roofInfoData, setRoofInfoData] = useState<SurveyDetailRequest>(roofInfoForm)
// 세션 데이터가 변경될 때 기본 정보 업데이트
useEffect(() => { useEffect(() => {
if (Number(idParam) !== 0 && surveyDetail === null) { if (!session?.isLoggedIn) return
alert('データが見つかりません。') setBasicInfoData((prev) => ({
window.location.href = '/survey-sale' ...prev,
} representative: session.userNm ?? '',
representativeId: session.userId ?? null,
store: session.storeNm ?? null,
storeId: session.storeId ?? null,
constructionPoint: session.builderNm ?? null,
constructionPointId: session.builderNo ?? null,
}))
}, [session?.isLoggedIn])
// 설문 데이터 로딩 및 업데이트
useEffect(() => {
if (isLoadingSurveyDetail || !session?.isLoggedIn) return
if (surveyDetail && (mode === 'EDIT' || mode === 'READ')) { if (surveyDetail && (mode === 'EDIT' || mode === 'READ')) {
const { id, uptDt, regDt, detailInfo, ...rest } = surveyDetail const { id, uptDt, regDt, detailInfo, ...rest } = surveyDetail
setBasicInfoData(rest) setBasicInfoData((prev) => ({
...prev,
...rest,
}))
if (detailInfo) { if (detailInfo) {
const { id, uptDt, regDt, basicInfoId, ...rest } = detailInfo const { id, uptDt, regDt, basicInfoId, ...rest } = detailInfo
setRoofInfoData(rest) setRoofInfoData(rest)
if (validateSurveyDetail(rest).trim() !== '') { if (validateSurveyDetail(rest).trim() !== '') {
// validation logic here if needed
} }
} }
} }
}, [surveyDetail, id]) }, [mode, session?.isLoggedIn, isLoadingSurveyDetail])
const data = { const data = {
basic: basicInfoData, basic: basicInfoData,
@ -105,9 +133,7 @@ export default function DetailForm() {
return ( return (
<> <>
<div className="sale-detail-toggle-wrap"> <div className="sale-detail-toggle-wrap">
{/* 기본정보 */} <BasicForm basicInfo={basicInfoData} setBasicInfo={setBasicInfoData} mode={mode} session={session} />
<BasicForm basicInfo={basicInfoData} setBasicInfo={setBasicInfoData} mode={mode} />
{/* 전기/지붕정보 */}
<RoofForm roofInfo={roofInfoData} setRoofInfo={setRoofInfoData} mode={mode} /> <RoofForm roofInfo={roofInfoData} setRoofInfo={setRoofInfoData} mode={mode} />
<ButtonForm {...buttonFormProps} /> <ButtonForm {...buttonFormProps} />
</div> </div>

View File

@ -137,7 +137,7 @@ export const radioEtcData: Record<RadioEtcKeys, { id: number; label: string }[]>
structureOrder: [ structureOrder: [
{ {
id: 1, id: 1,
label: '屋根材 - 防水材 - 屋根の基礎 - 垂木', //지붕재 방수재 지붕의기초 서까래 label: '屋根材 > 防水材 > 屋根の基礎 > 垂木', //지붕재 방수재 지붕의기초 서까래
}, },
], ],
houseStructure: [ houseStructure: [
@ -537,6 +537,7 @@ const SelectedBox = ({
</select> </select>
<div className={`data-input ${column === 'constructionYear' ? 'flex' : ''}`}> <div className={`data-input ${column === 'constructionYear' ? 'flex' : ''}`}>
<input <input
id={`${column}Etc`}
type={column === 'constructionYear' ? 'number' : 'text'} type={column === 'constructionYear' ? 'number' : 'text'}
inputMode={column === 'constructionYear' ? 'numeric' : 'text'} inputMode={column === 'constructionYear' ? 'numeric' : 'text'}
className="input-frame" className="input-frame"
@ -642,6 +643,7 @@ const RadioSelected = ({
{(showEtcOption || column === 'insulationPresence') && ( {(showEtcOption || column === 'insulationPresence') && (
<div className="data-input"> <div className="data-input">
<input <input
id={`${column}Etc`}
type="text" type="text"
className="input-frame" className="input-frame"
placeholder="-" placeholder="-"

View File

@ -115,8 +115,8 @@ export function useSuitable() {
hasNextPage, hasNextPage,
isFetchingNextPage, isFetchingNextPage,
isLoading, isLoading,
isError, // isError,
error, // error,
} = useInfiniteQuery<Suitable[]>({ } = useInfiniteQuery<Suitable[]>({
queryKey: ['suitables', 'list', selectedCategory, searchKeyword], queryKey: ['suitables', 'list', selectedCategory, searchKeyword],
queryFn: async (context) => { queryFn: async (context) => {
@ -158,7 +158,7 @@ export function useSuitable() {
const { const {
data: selectedSuitables, data: selectedSuitables,
isLoading: isSelectedSuitablesLoading, isLoading: isSelectedSuitablesLoading,
refetch: refetchSelectedSuitables, // refetch: refetchSelectedSuitables,
} = useQuery<Suitable[]>({ } = useQuery<Suitable[]>({
queryKey: ['suitables', 'selectedItems', getSelectedItemsHash(), selectedItemsSearching], queryKey: ['suitables', 'selectedItems', getSelectedItemsHash(), selectedItemsSearching],
queryFn: async () => { queryFn: async () => {
@ -198,6 +198,45 @@ export function useSuitable() {
return `${value}で設置可` return `${value}で設置可`
} }
const downloadSuitablePdf = async (): Promise<void> => {
try {
const { ids, detailIds } = serializeSelectedItems()
const fileTitle = `(${
suitableCommCode.get(SUITABLE_HEAD_CODE.ROOF_MATL_GRP_CD)?.find((category) => category.code === selectedCategory)?.codeJp
}) `
const form = document.createElement('form')
form.method = 'POST'
form.action = '/api/suitable/pdf'
form.target = '_blank'
const inputIds = document.createElement('input')
inputIds.type = 'hidden'
inputIds.name = 'ids'
inputIds.value = ids
const inputDetailIds = document.createElement('input')
inputDetailIds.type = 'hidden'
inputDetailIds.name = 'detailIds'
inputDetailIds.value = detailIds
const inputFileTitle = document.createElement('input')
inputFileTitle.type = 'hidden'
inputFileTitle.name = 'fileTitle'
inputFileTitle.value = fileTitle
form.appendChild(inputIds)
form.appendChild(inputDetailIds)
form.appendChild(inputFileTitle)
document.body.appendChild(form)
form.submit()
document.body.removeChild(form)
} catch (error) {
console.error('지붕재 상세 데이터 pdf 다운로드 실패:', error)
}
}
return { return {
getSuitables, getSuitables,
getSuitableIds, getSuitableIds,
@ -216,5 +255,6 @@ export function useSuitable() {
clearSuitableStore, clearSuitableStore,
suitableCheckIcon, suitableCheckIcon,
suitableCheckMemo, suitableCheckMemo,
downloadSuitablePdf,
} }
} }

View File

@ -1,10 +1,11 @@
import type { SurveyBasicInfo, SurveyDetailRequest, SurveyRegistRequest } from '@/types/Survey' import type { SurveyBasicInfo, SurveyDetailRequest, SurveyRegistRequest } from '@/types/Survey'
import { useMemo } from 'react' import { useMemo, useEffect } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useSurveyFilterStore } from '@/store/surveyFilterStore' import { useSurveyFilterStore } from '@/store/surveyFilterStore'
import { useSessionStore } from '@/store/session' import { useSessionStore } from '@/store/session'
import { useAxios } from './useAxios' import { useAxios } from './useAxios'
import { queryStringFormatter } from '@/utils/common-utils' import { queryStringFormatter } from '@/utils/common-utils'
import { useRouter } from 'next/navigation'
export const requiredFields = [ export const requiredFields = [
{ {
@ -66,7 +67,7 @@ export function useSurvey(id?: number): {
createSurvey: (survey: SurveyRegistRequest) => Promise<number> createSurvey: (survey: SurveyRegistRequest) => Promise<number>
updateSurvey: ({ survey, isTemporary, storeId }: { survey: SurveyRegistRequest; isTemporary: boolean; storeId?: string }) => void updateSurvey: ({ survey, isTemporary, storeId }: { survey: SurveyRegistRequest; isTemporary: boolean; storeId?: string }) => void
deleteSurvey: () => Promise<boolean> deleteSurvey: () => Promise<boolean>
submitSurvey: (params: { saveId?: number; targetId?: string; storeId?: string; srlNo?: string }) => void submitSurvey: (params: { targetId?: string | null; targetNm?: string | null }) => 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
@ -75,6 +76,7 @@ export function useSurvey(id?: number): {
const { keyword, searchOption, isMySurvey, sort, offset } = useSurveyFilterStore() const { keyword, searchOption, isMySurvey, sort, offset } = useSurveyFilterStore()
const { session } = useSessionStore() const { session } = useSessionStore()
const { axiosInstance } = useAxios() const { axiosInstance } = useAxios()
const router = useRouter()
const { const {
data: surveyListData, data: surveyListData,
@ -90,7 +92,7 @@ export function useSurvey(id?: number): {
isMySurvey, isMySurvey,
sort, sort,
offset, offset,
store: session?.storeId, storeId: session?.storeId,
builderNo: session?.builderNo, builderNo: session?.builderNo,
role: session?.role, role: session?.role,
}, },
@ -109,12 +111,24 @@ export function useSurvey(id?: number): {
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 (!session?.isLoggedIn || id === 0 || id === undefined) return null
if (id === null || isNaN(id)) return null try {
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 params: {
role: session?.role,
storeId: session?.storeId,
builderNo: session?.builderNo,
isLoggedIn: session?.isLoggedIn,
},
})
return resp.data
} catch (error: any) {
alert(error.response?.data.error)
router.replace('/survey-sale')
return null
}
}, },
enabled: id !== undefined, enabled: id !== 0 && id !== undefined && session?.isLoggedIn,
}) })
const { mutateAsync: createSurvey, isPending: isCreatingSurvey } = useMutation({ const { mutateAsync: createSurvey, isPending: isCreatingSurvey } = useMutation({
@ -163,13 +177,11 @@ export function useSurvey(id?: number): {
}) })
const { mutateAsync: submitSurvey, isPending: isSubmittingSurvey } = useMutation({ const { mutateAsync: submitSurvey, isPending: isSubmittingSurvey } = useMutation({
mutationFn: async ({ targetId, storeId, srlNo }: { targetId?: string; storeId?: string; srlNo?: string }) => { mutationFn: async ({ targetId, targetNm }: { targetId?: string | null; targetNm?: string | null }) => {
if (!id) throw new Error('id is required') if (!id) throw new Error('id is required')
const resp = await axiosInstance(null).patch<boolean>(`/api/survey-sales/${id}`, { const resp = await axiosInstance(null).patch<boolean>(`/api/survey-sales/${id}`, {
targetId, targetId,
storeId, targetNm,
srlNo,
role: session?.role ?? null,
}) })
return resp.data return resp.data
}, },
@ -180,34 +192,44 @@ export function useSurvey(id?: number): {
}) })
const validateSurveyDetail = (surveyDetail: SurveyDetailRequest) => { const validateSurveyDetail = (surveyDetail: SurveyDetailRequest) => {
const etcFields = [ // 상수 정의
'installationSystem', const ETC_FIELDS = ['installationSystem', 'rafterSize', 'rafterPitch', 'waterproofMaterial', 'structureOrder'] as const
'constructionYear',
'rafterSize',
'rafterPitch',
'waterproofMaterial',
'structureOrder',
'insulationPresence',
] as const
const emptyField = requiredFields.find((field) => { const SPECIAL_CONDITIONS = ['constructionYear', 'insulationPresence'] as const
if (etcFields.includes(field.field as (typeof etcFields)[number])) {
return (
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 {
return surveyDetail[field.field as keyof SurveyDetailRequest] === null
}
})
const contractCapacity = surveyDetail.contractCapacity // 유틸리티 함수들
if (contractCapacity && contractCapacity.trim() !== '' && contractCapacity.split(' ')?.length === 1) { const isEmptyValue = (value: any): boolean => {
return 'contractCapacityUnit' return value === null || value?.toString().trim() === ''
} }
return emptyField?.field || '' const checkRequiredField = (field: string): string => {
if (ETC_FIELDS.includes(field as (typeof ETC_FIELDS)[number])) {
if (
isEmptyValue(surveyDetail[field as keyof SurveyDetailRequest]) &&
isEmptyValue(surveyDetail[`${field}Etc` as keyof SurveyDetailRequest])
) {
return field
}
} else if (SPECIAL_CONDITIONS.includes(field as (typeof SPECIAL_CONDITIONS)[number])) {
if (surveyDetail[field as keyof SurveyDetailRequest] === '2' && isEmptyValue(surveyDetail[`${field}Etc` as keyof SurveyDetailRequest])) {
return `${field}Etc`
} else if (isEmptyValue(surveyDetail[field as keyof SurveyDetailRequest])) {
return field
}
}
return ''
}
// 필수 필드 체크
const emptyField = requiredFields.find((field) => checkRequiredField(field.field))
if (emptyField) return emptyField.field
// 계약 용량 단위 체크
const contractCapacity = surveyDetail.contractCapacity
if (contractCapacity?.trim() && contractCapacity.split(' ').length === 1) {
return 'contractCapacityUnit'
}
return ''
} }
const getZipCode = async (zipCode: string): Promise<ZipCode[] | null> => { const getZipCode = async (zipCode: string): Promise<ZipCode[] | null> => {

View File

@ -21,18 +21,18 @@ export const SEARCH_OPTIONS = [
id: 'store', id: 'store',
label: '販売店名', label: '販売店名',
}, },
// { {
// id: 'store_id', id: 'store_id',
// label: '販売店ID', label: '販売店ID',
// }, },
{ {
id: 'construction_point', id: 'construction_point',
label: '施工店名', label: '施工店名',
}, },
// { {
// id: 'construction_id', id: 'construction_point_id',
// label: '施工店ID', label: '施工店ID',
// }, },
] ]
export const SEARCH_OPTIONS_PARTNERS = [ export const SEARCH_OPTIONS_PARTNERS = [

View File

@ -58,11 +58,22 @@
} }
// 지붕재 적합성 // 지붕재 적합성
.pdf-table-wrap{
max-width: 1540px;
min-width: 1540px;
margin: 0 auto;
}
.pdf-intro-page{ .pdf-intro-page{
height: 1080px; display: flex;
flex-direction: column;
height: 1050px;
padding: 80px 40px ; padding: 80px 40px ;
background-color: #fff; background-color: #fff;
} }
.pdf-intro-foot-date{
margin-top: auto;
text-align: right;
}
.pdf-intro-tit-wrap{ .pdf-intro-tit-wrap{
text-align: center; text-align: center;
.pdf-intro-tit{ .pdf-intro-tit{
@ -80,7 +91,11 @@
} }
.pdf-table-content{ .pdf-table-content{
display: flex;
flex-direction: column;
height: 1050px;
padding: 20px; padding: 20px;
margin-bottom: 50px;
} }
.pdf-table-grid-wrap{ .pdf-table-grid-wrap{
display: grid; display: grid;

View File

@ -1,38 +1,38 @@
@use '../abstracts' as *; @use "../abstracts" as *;
// input form 공통 // input form 공통
.data-input-form-bx { .data-input-form-bx{
margin-bottom: 18px; margin-bottom: 18px;
&:last-child { &:last-child{
margin-bottom: 0; margin-bottom: 0;
} }
.data-input-form-tit { .data-input-form-tit{
@include defaultFont($font-s-13, $font-w-500, $font-c); @include defaultFont($font-s-13, $font-w-500, $font-c);
margin-bottom: 10px; margin-bottom: 10px;
.import { .import{
color: #f00; color: #F00;
} }
span { span{
display: block; display: block;
@include defaultFont($font-s-13, $font-w-400, #a8b6c7); @include defaultFont($font-s-13, $font-w-400, #A8B6C7);
} }
} }
.data-input-guide { .data-input-guide{
margin-top: 8px; margin-top: 8px;
@include defaultFont($font-s-13, $font-w-400, #a8b6c7); @include defaultFont($font-s-13, $font-w-400, #A8B6C7);
} }
} }
.btn-flex-wrap { .btn-flex-wrap{
@include flex(5px); @include flex(5px);
margin-top: 24px; margin-top: 24px;
.btn-bx { .btn-bx{
flex: 1; flex: 1;
} }
&.com { &.com{
.btn-bx { .btn-bx{
flex: 1 1 auto; flex: 1 1 auto;
button { button{
font-size: 12px; font-size: 12px;
} }
} }
@ -40,13 +40,13 @@
} }
// 매물 common // 매물 common
.top-btn { .top-btn{
position: fixed; position: fixed;
bottom: 96px; bottom: 96px;
right: 15px; right: 15px;
width: 38px; width: 38px;
height: 38px; height: 38px;
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.50);
background-image: url(/assets/images/sub/top_btn_icon.svg); background-image: url(/assets/images/sub/top_btn_icon.svg);
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
@ -55,68 +55,68 @@
z-index: 90000; z-index: 90000;
} }
.sale-contents { .sale-contents{
width: 100%; width: 100%;
background-color: #f5f5f5; background-color: #F5F5F5;
.sale-frame { .sale-frame{
padding: 0 20px; padding: 0 20px;
border-top: 1px solid #ececec; border-top: 1px solid #ECECEC;
border-bottom: 1px solid #ececec; border-bottom: 1px solid #ECECEC;
margin-bottom: 10px; margin-bottom: 10px;
padding-bottom: 24px; padding-bottom: 24px;
padding-top: 24px; padding-top: 24px;
background-color: $white-fff; background-color: $white-fff;
&:first-child { &:first-child{
padding-top: 0; padding-top: 0;
border-top: none; border-top: none;
} }
&:last-child { &:last-child{
padding-bottom: 0; padding-bottom: 0;
border-bottom: none; border-bottom: none;
margin-bottom: 0; margin-bottom: 0;
} }
} }
} }
.sale-form-btn-wrap { .sale-form-btn-wrap{
padding: 20px 20px 0; padding: 20px 20px 0 ;
background-color: #fff; background-color: #fff;
.btn-flex-wrap { .btn-flex-wrap{
margin-top: 0; margin-top: 0;
} }
} }
// 매물 목록 // 매물 목록
.sale-form-bx { .sale-form-bx{
margin-bottom: 14px; margin-bottom: 14px;
&:last-child { &:last-child{
margin-bottom: 0; margin-bottom: 0;
} }
} }
.sale-list-wrap { .sale-list-wrap{
.sale-list-item { .sale-list-item{
padding-top: 14px; padding-top: 14px;
padding-bottom: 14px; padding-bottom: 14px;
border-bottom: 1px solid #ececec; border-bottom: 1px solid #ECECEC;
cursor: pointer; cursor: pointer;
&:first-child { &:first-child{
padding-top: 0; padding-top: 0;
} }
&:last-child { &:last-child{
border-bottom: none; border-bottom: none;
padding-bottom: 0; padding-bottom: 0;
} }
} }
} }
.sale-item-bx { .sale-item-bx{
.sale-item-date-bx { .sale-item-date-bx{
@include flex(0px); @include flex(0px);
align-items: center; align-items: center;
margin-bottom: 9px; margin-bottom: 9px;
.sale-item-num { .sale-item-num{
position: relative; position: relative;
@include defaultFont($font-s-13, $font-w-400, $font-c); @include defaultFont($font-s-13, $font-w-400, $font-c);
padding-right: 6px; padding-right: 6px;
&::after { &::after{
content: ''; content: '';
position: absolute; position: absolute;
top: 50%; top: 50%;
@ -124,31 +124,31 @@
transform: translateY(-50%); transform: translateY(-50%);
width: 1px; width: 1px;
height: 10px; height: 10px;
background-color: #a2abb8; background-color: #A2ABB8;
} }
} }
.sale-item-date { .sale-item-date{
@include defaultFont($font-s-13, $font-w-400, #a2abb8); @include defaultFont($font-s-13, $font-w-400, #A2ABB8);
padding-left: 6px; padding-left: 6px;
} }
} }
.sale-item-tit { .sale-item-tit{
@include defaultFont($font-s-15, $font-w-500, $font-c); @include defaultFont($font-s-15, $font-w-500, $font-c);
@include ellipsis(1); @include ellipsis(1);
margin-bottom: 9px; margin-bottom: 9px;
} }
.sale-item-customer { .sale-item-customer{
@include defaultFont($font-s-13, $font-w-400, $font-c); @include defaultFont($font-s-13, $font-w-400, $font-c);
margin-bottom: 9px; margin-bottom: 9px;
} }
.sale-item-update-bx { .sale-item-update-bx{
@include flex(0px); @include flex(0px);
align-items: center; align-items: center;
.sale-item-name { .sale-item-name{
position: relative; position: relative;
@include defaultFont($font-s-13, $font-w-400, #a2abb8); @include defaultFont($font-s-13, $font-w-400, #A2ABB8);
padding-right: 6px; padding-right: 6px;
&::after { &::after{
content: ''; content: '';
position: absolute; position: absolute;
top: 50%; top: 50%;
@ -156,177 +156,176 @@
transform: translateY(-50%); transform: translateY(-50%);
width: 1px; width: 1px;
height: 10px; height: 10px;
background-color: #a2abb8; background-color: #A2ABB8;
} }
} }
.sale-item-update { .sale-item-update{
@include defaultFont($font-s-13, $font-w-400, #a2abb8); @include defaultFont($font-s-13, $font-w-400, #A2ABB8);
padding-left: 6px; padding-left: 6px;
} }
} }
&.nodata { &.nodata{
.sale-item-nodata { .sale-item-nodata{
padding: 5px 0; padding: 5px 0;
text-align: center; text-align: center;
@include defaultFont($font-s-15, $font-w-500, $font-c); @include defaultFont($font-s-15, $font-w-500, $font-c);
} }
} }
} }
.sale-edit-btn { .sale-edit-btn{
margin-top: 24px; margin-top: 24px;
} }
// 매물 상세 // 매물 상세
.sale-data-table-wrap { .sale-data-table-wrap{
padding: 24px; padding: 24px;
background-color: #fff; background-color: #fff;
border-top: 1px solid #ececec; border-top: 1px solid #ECECEC;
} }
.sale-data-table { .sale-data-table{
width: 100%; width: 100%;
table-layout: fixed; table-layout: fixed;
tbody { tbody{
tr { tr{
th { th{
@include defaultFont($font-s-13, $font-w-500, $font-c); @include defaultFont($font-s-13, $font-w-500, $font-c);
vertical-align: top; vertical-align: top;
padding: 5px 0; padding: 5px 0;
} }
td { td{
@include defaultFont($font-s-13, $font-w-400, $font-c); @include defaultFont($font-s-13, $font-w-400, $font-c);
padding: 5px 0 8px 14px; padding: 5px 0 8px 14px;
.data-down { .data-down{
@include flex(8px); @include flex(8px);
align-items: center; align-items: center;
color: #1259cb; color: #1259CB;
i { i{
display: block; display: block;
width: 8px; width: 8px;
height: 12px; height: 12px;
background: url(/assets/images/sub/down_icon.svg) no-repeat center; background: url(/assets/images/sub/down_icon.svg)no-repeat center;
background-size: cover; background-size: cover;
} }
} }
} }
&:first-child { &:first-child{
th, th,td{
td {
padding-top: 0; padding-top: 0;
} }
} }
&:last-child { &:last-child{
th, th,td{
td {
padding-bottom: 0; padding-bottom: 0;
} }
} }
} }
} }
} }
.sale-detail-toggle-wrap { .sale-detail-toggle-wrap{
border-top: 1px solid #ececec; border-top: 1px solid #ECECEC;
} }
.sale-detail-toggle-bx { .sale-detail-toggle-bx{
border-bottom: 1px solid #ececec; border-bottom: 1px solid #ECECEC;
} }
.sale-detail-toggle-head { .sale-detail-toggle-head{
@include flex(5px); @include flex(5px);
padding: 14px 18px; padding: 14px 18px;
background-color: $white-fff; background-color: $white-fff;
cursor: pointer; cursor: pointer;
.sale-detail-toggle-name { .sale-detail-toggle-name{
@include defaultFont($font-s-13, $font-w-500, $font-c); @include defaultFont($font-s-13, $font-w-500, $font-c);
} }
.sale-detail-toggle-btn-wrap { .sale-detail-toggle-btn-wrap{
margin-left: auto; margin-left: auto;
.sale-detail-toggle-btn { .sale-detail-toggle-btn{
display: block; display: block;
width: 22px; width: 22px;
height: 22px; height: 22px;
background: url(/assets/images/sub/sale_toggle_btn.svg) no-repeat center; background: url(/assets/images/sub/sale_toggle_btn.svg)no-repeat center;
background-size: cover; background-size: cover
} }
} }
} }
.sale-detail-toggle-cont { .sale-detail-toggle-cont{
display: none; display: none;
.sale-frame { .sale-frame{
padding: 24px 20px; padding: 24px 20px;
&:first-child { &:first-child{
padding-top: 24px; padding-top: 24px;
} }
&:last-child { &:last-child{
padding-bottom: 24px; padding-bottom: 24px;
} }
} }
} }
.sale-detail-toggle-bx { .sale-detail-toggle-bx{
&.act { &.act{
.sale-detail-toggle-head { .sale-detail-toggle-head{
background-color: #5f738e; background-color: #5F738E;
.sale-detail-toggle-name { .sale-detail-toggle-name{
color: #fff; color: #fff
} }
.sale-detail-toggle-btn-wrap { .sale-detail-toggle-btn-wrap{
.sale-detail-toggle-btn { .sale-detail-toggle-btn{
background: url(/assets/images/sub/sale_toggle_btn_white.svg) no-repeat center; background: url(/assets/images/sub/sale_toggle_btn_white.svg)no-repeat center;
} }
} }
} }
.sale-detail-toggle-cont { .sale-detail-toggle-cont{
display: block; display: block;
} }
} }
} }
// 매물 기본정보 // 매물 기본정보
.form-flex { .form-flex{
@include flex(5px); @include flex(5px);
.form-bx { .form-bx{
flex: 1; flex: 1;
} }
} }
.form-btn { .form-btn{
margin-top: 12px; margin-top: 12px;
} }
// 매물 전기 지붕정보 // 매물 전기 지붕정보
.sale-roof-title { .sale-roof-title{
@include defaultFont($font-s-15, $font-w-500, $font-c); @include defaultFont($font-s-15, $font-w-500, $font-c);
padding-bottom: 10px; padding-bottom: 10px;
margin-bottom: 20px; margin-bottom: 20px;
border-bottom: 1px solid #2e3a59; border-bottom: 1px solid #2E3A59;
} }
.data-check-wrap { .data-check-wrap{
@include flex(10px); @include flex(10px);
flex-wrap: wrap; flex-wrap: wrap;
margin-bottom: 12px; margin-bottom: 12px;
.radio-form-box, .radio-form-box,
.check-form-box { .check-form-box{
width: calc(50% - 5px); width: calc(50% - 5px);
} }
&.mb0 { &.mb0{
margin-bottom: 0; margin-bottom: 0;
} }
} }
.data-input { .data-input{
&.flex { &.flex{
@include flex(8px); @include flex(8px);
align-items: center; align-items: center;
span { span{
flex: none; flex: none;
@include defaultFont($font-s-13, $font-w-400, $font-c); @include defaultFont($font-s-13, $font-w-400, $font-c);
} }
} }
} }
// 1:1 문의 common // 1:1 문의 common
.inquiry-frame { .inquiry-frame{
padding: 0 20px; padding: 0 20px;
} }
.badge { .badge{
min-width: 60px; min-width: 60px;
height: 30px; height: 30px;
line-height: 30px; line-height: 30px;
@ -335,64 +334,65 @@
text-align: center; text-align: center;
font-size: $font-s-12; font-size: $font-s-12;
font-weight: $font-w-500; font-weight: $font-w-500;
&.blue { &.blue{
color: #5497e9; color: #5497E9;
background-color: #ecf5ff; background-color: #ECF5FF;
} }
&.orange { &.orange{
color: #f86a56; color: #F86A56;
background-color: #ffefed; background-color: #FFEFED;
} }
&.block { &.block{
width: 100%; width: 100%;
} }
} }
// 1:1 문의 목록 // 1:1 문의 목록
.inquiry-table-filter { .inquiry-table-filter{
margin-bottom: 24px; margin-bottom: 24px;
.filter-check { .filter-check{
margin-bottom: 12px; margin-bottom: 12px;
} }
} }
.inquiry-list-tit { .inquiry-list-tit{
padding-bottom: 10px; padding-bottom: 10px;
border-bottom: 1px solid #2e3a59; border-bottom: 1px solid #2E3A59;
@include defaultFont($font-s-13, $font-w-400, $font-c); @include defaultFont($font-s-13, $font-w-400, $font-c);
span { span{
font-weight: $font-w-500; font-weight: $font-w-500;
} }
} }
.inquiry-list { .inquiry-list{
.inquiry-item { .inquiry-item{
padding: 10px 0; padding: 10px 0;
cursor: pointer; cursor: pointer;
border-bottom: 1px solid #ececec; border-bottom: 1px solid #ECECEC;
&:last-child { &:last-child{
border-bottom: none; border-bottom: none;
padding-bottom: 0; padding-bottom: 0;
} }
.inquiry-item-bx { .inquiry-item-bx{
position: relative; position: relative;
padding-right: 70px; padding-right: 70px;
.inquiry-item-category { .inquiry-item-category{
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 5px; margin-bottom: 5px;
span { span{
position: relative; position: relative;
display: block; display: block;
@include defaultFont($font-s-13, $font-w-400, $font-c); @include defaultFont($font-s-13, $font-w-400, $font-c);
padding: 0 6px; padding: 0 6px;
&:first-child { &:first-child{
padding-left: 0; padding-left: 0;
} }
&:last-child { &:last-child{
padding-right: 0; padding-right: 0;
&::before { &::before{
display: none; display: none;
} }
} }
&::before { &::before{
content: ''; content: '';
position: absolute; position: absolute;
top: 50%; top: 50%;
@ -400,31 +400,26 @@
transform: translateY(-50%); transform: translateY(-50%);
width: 1px; width: 1px;
height: 10px; height: 10px;
background-color: #a2abb8; background-color: #A2ABB8;
} }
} }
} }
.inquiry-item-tit { .inquiry-item-tit{
@include defaultFont($font-s-15, $font-w-500, $font-c); @include defaultFont($font-s-15, $font-w-500, $font-c);
@include ellipsis(1); @include ellipsis(1);
margin-bottom: 5px; margin-bottom: 5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
display: block;
} }
.inquiry-item-date { .inquiry-item-date{
@include defaultFont($font-s-13, $font-w-400, #a2abb8); @include defaultFont($font-s-13, $font-w-400, #A2ABB8);
} }
.inquiry-badge { .inquiry-badge{
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
} }
&.nodata { &.nodata{
padding-right: 0; padding-right: 0;
.inquiry-item-nodata { .inquiry-item-nodata{
padding: 10px 0; padding: 10px 0;
text-align: center; text-align: center;
@include defaultFont($font-s-15, $font-w-500, $font-c); @include defaultFont($font-s-15, $font-w-500, $font-c);
@ -435,45 +430,42 @@
} }
// 1:1문의 작성 // 1:1문의 작성
.textarea-form { .inquiry-file-wrap{
white-space: pre-wrap;
}
.inquiry-file-wrap {
margin-top: 20px; margin-top: 20px;
.file-list-wrap { .file-list-wrap{
margin-top: 14px; margin-top: 14px;
} }
} }
.file-list-tit { .file-list-tit{
@include defaultFont($font-s-13, $font-w-500, $font-c); @include defaultFont($font-s-13, $font-w-500, $font-c);
} }
.file-list { .file-list{
margin-top: 14px; margin-top: 14px;
.file-item { .file-item{
border-top: 1px solid #ededed; border-top: 1px solid #EDEDED;
cursor: default; cursor: default;
.file-item-bx { .file-item-bx{
width: 100%; width: 100%;
padding: 14px 0; padding: 14px 0;
@include flex(0px); @include flex(0px);
align-items: center; align-items: center;
.file-item-name { .file-item-name{
@include ellipsis(1); @include ellipsis(1);
@include defaultFont($font-s-13, $font-w-400, $font-c); @include defaultFont($font-s-13, $font-w-400, $font-c);
padding-right: 10px; padding-right: 10px;
} }
.file-del { .file-del{
flex: none; flex: none;
display: block; display: block;
margin-left: auto; margin-left: auto;
width: 16px; width: 16px;
height: 16px; height: 16px;
background: url(/assets/images/common/id_delete_icon.svg) no-repeat center; background: url(/assets/images/common/id_delete_icon.svg)no-repeat center;
background-size: cover; background-size: cover;
} }
} }
&:last-child { &:last-child{
.file-item-bx { .file-item-bx{
padding-bottom: 0; padding-bottom: 0;
} }
} }
@ -481,33 +473,33 @@
} }
// 1:1 문의 상세 // 1:1 문의 상세
.inquiry-detail-data-table { .inquiry-detail-data-table{
padding: 20px 0; padding: 20px 0;
border-bottom: 1px solid #ececec; border-bottom: 1px solid #ECECEC;
} }
.inquiry-detail-data { .inquiry-detail-data{
padding: 20px 0; padding: 20px 0;
border-bottom: 1px solid #2e3a59; border-bottom: 1px solid #2E3A59;
margin-bottom: 24px; margin-bottom: 24px;
.inquiry-detail-category { .inquiry-detail-category{
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 3px; margin-bottom: 3px;
span { span{
position: relative; position: relative;
display: block; display: block;
@include defaultFont($font-s-13, $font-w-400, $font-c); @include defaultFont($font-s-13, $font-w-400, $font-c);
padding: 0 6px; padding: 0 6px;
&:first-child { &:first-child{
padding-left: 0; padding-left: 0;
} }
&:last-child { &:last-child{
padding-right: 0; padding-right: 0;
&::before { &::before{
display: none; display: none;
} }
} }
&::before { &::before{
content: ''; content: '';
position: absolute; position: absolute;
top: 50%; top: 50%;
@ -515,154 +507,152 @@
transform: translateY(-50%); transform: translateY(-50%);
width: 1px; width: 1px;
height: 10px; height: 10px;
background-color: #a2abb8; background-color: #A2ABB8;
} }
} }
} }
.inquiry-detail-tit { .inquiry-detail-tit{
@include defaultFont($font-s-15, $font-w-500, $font-c); @include defaultFont($font-s-15, $font-w-500, $font-c);
margin-bottom: 10px; margin-bottom: 10px;
word-wrap: break-word;
white-space: normal;
overflow-wrap: break-word;
} }
.inquiry-detail-txt { .inquiry-detail-txt{
@include defaultFont($font-s-13, $font-w-400, $font-c); @include defaultFont($font-s-13, $font-w-400, $font-c);
white-space: pre-line; white-space: pre-line;
} }
} }
// 1:1 문의 답변 // 1:1 문의 답변
.inquiry-answer-wrap { .inquiry-answer-wrap{
margin-top: 24px; margin-top: 24px;
} }
.inquiry-answer-header { .inquiry-answer-header{
padding: 20px 0; padding: 20px 0;
border-top: 1px solid #f86a56; border-top: 1px solid #F86A56;
border-bottom: 1px solid #ececec; border-bottom: 1px solid #ECECEC;
.inquiry-answer-tit { .inquiry-answer-tit{
@include defaultFont($font-s-14, $font-w-500, #f86a56); @include defaultFont($font-s-14, $font-w-500, #F86A56);
margin-bottom: 5px; margin-bottom: 5px;
} }
.inquiry-answer-date { .inquiry-answer-date{
@include defaultFont($font-s-13, $font-w-400, #f86a56); @include defaultFont($font-s-13, $font-w-400, #F86A56);
} }
} }
.inquiry-answer-tit { .inquiry-answer-tit{
@include defaultFont($font-s-13, $font-w-400, $font-c); @include defaultFont($font-s-13, $font-w-400, $font-c);
margin-bottom: 3px; margin-bottom: 3px;
} }
// 비밀번호 변경 // 비밀번호 변경
.border-frame { .border-frame{
padding: 20px; padding: 20px;
border-top: 1px solid #ececec; border-top: 1px solid #ECECEC;
border-bottom: 1px solid #ececec; border-bottom: 1px solid #ECECEC;
background-color: #fff; background-color: #fff;
margin-bottom: 10px; margin-bottom: 10px;
&:last-child { &:last-child{
border-bottom: none; border-bottom: none;
padding-bottom: 0; padding-bottom: 0;
margin-bottom: 0; margin-bottom: 0;
} }
} }
.pw-guide { .pw-guide{
.pw-guide-tit { .pw-guide-tit{
@include defaultFont($font-s-16, $font-w-500, #1259cb); @include defaultFont($font-s-16, $font-w-500, #1259CB);
} }
.pw-guide-txt { .pw-guide-txt{
@include defaultFont($font-s-13, $font-w-400, #417ddc); @include defaultFont($font-s-13, $font-w-400, #417DDC);
} }
} }
// 지붕재 적합성 // 지붕재 적합성
.compliance-icon { .compliance-icon{
display: flex; display: flex;
} }
.compliance-check-wrap { .compliance-check-wrap{
padding-top: 10px; padding-top: 10px;
} }
.compliance-check-bx { .compliance-check-bx{
position: relative; position: relative;
padding: 14px 18px; padding: 14px 18px;
border: 1px solid #efefef; border: 1px solid #EFEFEF;
border-radius: 4px; border-radius: 4px;
margin-bottom: 10px; margin-bottom: 10px;
&:last-child { &:last-child{
margin-bottom: 0; margin-bottom: 0;
} }
&.act { &.act{
.bx-btn { .bx-btn{
transform: rotate(0) !important; transform: rotate(0) !important;
} }
.reference-list { .reference-list{
display: block; display: block
} }
} }
} }
.check-name-wrap { .check-name-wrap{
@include flex(0px); @include flex(0px);
align-items: center; align-items: center;
.check-name { .check-name{
@include defaultFont($font-s-13, $font-w-500, $font-c); @include defaultFont($font-s-13, $font-w-500, $font-c);
} }
.check-name-btn { .check-name-btn{
padding-left: 5px; padding-left: 5px;
margin-left: auto; margin-left: auto;
.bx-btn { .bx-btn{
display: block; display: block;
width: 22px; width: 22px;
height: 22px; height: 22px;
background: url(/assets/images/sub/compliance_bx_icon.svg) no-repeat center; background: url(/assets/images/sub/compliance_bx_icon.svg)no-repeat center;
transform: rotate(180deg); transform: rotate(180deg);
} }
} }
} }
.reference-list { .reference-list{
display: none; display: none;
margin-top: 10px; margin-top: 10px;
padding-top: 14px; padding-top: 14px;
border-top: 1px solid #ececec; border-top: 1px solid #ECECEC;
transition: all 0.15s ease-in-out; transition: all .15s ease-in-out;
.reference-item { .reference-item{
margin-bottom: 8px; margin-bottom: 8px;
padding-left: 14px; padding-left: 14px;
.reference-item-bx { .reference-item-bx{
@include flex(10px); @include flex(10px);
@include defaultFont($font-s-13, $font-w-400, $font-c); @include defaultFont($font-s-13, $font-w-400, $font-c);
align-items: center; align-items: center;
} }
&:last-child { &:last-child{
margin-bottom: 0; margin-bottom: 0;
} }
} }
&.check { &.check{
.reference-item { .reference-item{
margin-bottom: 14px; margin-bottom: 14px;
} }
} }
} }
.compliace-nosearch { .compliace-nosearch{
padding: 30px 0; padding: 30px 0;
span { span{
display: block; display: block;
@include defaultFont($font-s-13, $font-w-400, $font-c); @include defaultFont($font-s-13, $font-w-400, $font-c);
text-align: center; text-align: center;
} }
} }
.check-item-wrap { .check-item-wrap{
@include flex(0px); @include flex(0px);
align-items: center; align-items: center;
} }
.compliance-icon-wrap { .compliance-icon-wrap{
margin-left: auto; margin-left: auto;
min-width: 44px; min-width: 44px;
@include flex(0px); @include flex(0px);
align-items: center; align-items: center;
} }
.float-btn-wrap { .float-btn-wrap{
position: sticky; position: sticky;
bottom: 10px; bottom: 10px;
left: 0; left: 0;
@ -670,14 +660,14 @@
background-color: #fff; background-color: #fff;
z-index: 9; z-index: 9;
} }
@media screen and (max-width: 360px) { @media screen and (max-width: 360px){
.btn-flex-wrap { .btn-flex-wrap{
flex-direction: column; flex-direction: column;
} }
.data-check-wrap { .data-check-wrap{
.radio-form-box, .radio-form-box,
.check-form-box { .check-form-box{
width: 100%; width: 100%;
} }
} }
} }

View File

@ -5,6 +5,7 @@ export type SurveyBasicInfo = {
store: string | null store: string | null
storeId: string | null storeId: string | null
constructionPoint: string | null constructionPoint: string | null
constructionPointId: string | null
investigationDate: string | null investigationDate: string | null
buildingName: string | null buildingName: string | null
customerName: string | null customerName: string | null
@ -17,6 +18,7 @@ export type SurveyBasicInfo = {
regDt: Date regDt: Date
uptDt: Date uptDt: Date
submissionTargetId: string | null submissionTargetId: string | null
submissionTargetNm: string | null
srlNo: string | null //판매점IDyyMMdd000 srlNo: string | null //판매점IDyyMMdd000
} }
@ -68,6 +70,7 @@ export type SurveyBasicRequest = {
store: string | null store: string | null
storeId: string | null storeId: string | null
constructionPoint: string | null constructionPoint: string | null
constructionPointId: string | null
investigationDate: string | null investigationDate: string | null
buildingName: string | null buildingName: string | null
customerName: string | null customerName: string | null
@ -77,6 +80,7 @@ export type SurveyBasicRequest = {
submissionStatus: boolean submissionStatus: boolean
submissionDate: string | null submissionDate: string | null
submissionTargetId: string | null submissionTargetId: string | null
submissionTargetNm: string | null
srlNo: string | null //판매점IDyyMMdd000 srlNo: string | null //판매점IDyyMMdd000
} }