Compare commits
No commits in common. "83bdba2cd51bed8b897372225912bd6e8fc03225" and "7f14135616c4dca19eb33dae1691377825e4cc96" have entirely different histories.
83bdba2cd5
...
7f14135616
@ -5,13 +5,12 @@ NEXT_PUBLIC_RUN_MODE=development
|
||||
NEXT_PUBLIC_API_URL=http://localhost:3000
|
||||
|
||||
#qsp 로그인 api
|
||||
NEXT_PUBLIC_QSP_API_URL=http://121.168.9.37:8080
|
||||
NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120
|
||||
# NEXT_PUBLIC_QSP_API_URL=https://jp-dev.qsalesplatform.com
|
||||
|
||||
#1:1문의 api
|
||||
NEXT_PUBLIC_INQUIRY_API_URL=https://jp-dev.qsalesplatform.com
|
||||
|
||||
EMAIL_TITLE_PREFIX=(System Test)
|
||||
|
||||
#QPARTNER 로그인 api
|
||||
DB_HOST=202.218.61.226
|
||||
|
||||
@ -11,8 +11,6 @@ NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120
|
||||
#1:1문의 api
|
||||
NEXT_PUBLIC_INQUIRY_API_URL=https://jp-dev.qsalesplatform.com
|
||||
|
||||
EMAIL_TITLE_PREFIX=
|
||||
|
||||
#QPARTNER 로그인 api
|
||||
DB_HOST=202.218.61.226
|
||||
DB_USER=readonly
|
||||
|
||||
@ -9,8 +9,6 @@ NEXT_PUBLIC_QSP_API_URL=https://jp.qsalesplatform.com
|
||||
#1:1문의 api
|
||||
NEXT_PUBLIC_INQUIRY_API_URL=http://172.23.4.129:8110
|
||||
|
||||
EMAIL_TITLE_PREFIX=
|
||||
|
||||
#QPARTNER 로그인 api
|
||||
DB_HOST=202.218.61.226
|
||||
DB_USER=readonly
|
||||
|
||||
107
README.md
107
README.md
@ -1,71 +1,4 @@
|
||||
# 모바일 현장 조사 애플리케이션 (onsitesurvey)
|
||||
|
||||
## 개요
|
||||
|
||||
Next.js와 TypeScript로 구축된 현장 조사용 모바일 애플리케이션입니다.
|
||||
|
||||
## 기술 스택
|
||||
|
||||
- **프레임워크**: Next.js (App Router)
|
||||
- **언어**: TypeScript
|
||||
- **UI 컴포넌트**: Shadcn UI, Radix UI
|
||||
- **스타일링**: Tailwind CSS
|
||||
- **상태 관리**: React Query
|
||||
- **데이터베이스**: Prisma ORM
|
||||
- **인증**: 세션 기반 인증
|
||||
|
||||
## 시작하기
|
||||
|
||||
### 필수 요구사항
|
||||
|
||||
- Node.js (LTS 버전)
|
||||
- npm 또는 yarn 또는 pnpm 또는 burn
|
||||
- Prisma CLI
|
||||
|
||||
### 설치 방법
|
||||
|
||||
```bash
|
||||
# 의존성 설치
|
||||
npm install
|
||||
|
||||
# 환경 변수 설정
|
||||
cp .env.example .env.local
|
||||
|
||||
```
|
||||
|
||||
### 개발 서버 실행
|
||||
|
||||
```bash
|
||||
# 개발 서버 실행
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 프로덕션 빌드
|
||||
|
||||
```bash
|
||||
# 애플리케이션 빌드
|
||||
npm run build
|
||||
|
||||
# 프로덕션 서버 실행
|
||||
npm start
|
||||
```
|
||||
|
||||
## 프로젝트 구조
|
||||
|
||||
[프로젝트 구조 Diagram](./diagram/mermaid.md)
|
||||
|
||||
```
|
||||
├── app/ # Next.js 앱 디렉토리
|
||||
├── components/ # React 컴포넌트
|
||||
├── hooks/ # 커스텀 React 훅
|
||||
├── lib/ # 유틸리티 함수 및 설정
|
||||
├── prisma/ # 데이터베이스 스키마 및 마이그레이션
|
||||
└── public/ # 정적 자산
|
||||
```
|
||||
|
||||
## 참고
|
||||
|
||||
### prisma 연결
|
||||
# prisma 연결
|
||||
|
||||
```
|
||||
npx prisma migrate dev
|
||||
@ -78,38 +11,38 @@ npx prisma db push
|
||||
|
||||
generate 를 진행해야 로컬에 연결 파일들이 생성이되고 pull push 를 사용할 수 있게 됨.
|
||||
|
||||
### react query cache data 가져오기
|
||||
# react query cache data 가져오기
|
||||
|
||||
```
|
||||
const cache = useQueryClient()
|
||||
const data = cache.getQueryData(['user', 'info']) as UserState
|
||||
```
|
||||
|
||||
### 팝업 컨트롤러 제어
|
||||
# 팝업 컨트롤러 제어
|
||||
|
||||
[open]
|
||||
### open
|
||||
|
||||
```javascript
|
||||
```
|
||||
const popupController = usePopupController()
|
||||
|
||||
onClick={() => popupController.setMemberInformationPopup(true)}
|
||||
onClick={() => popupController.setZipCodePopup(true)}
|
||||
```
|
||||
|
||||
[close]
|
||||
### close
|
||||
|
||||
```javascript
|
||||
```
|
||||
const popupController = usePopupController()
|
||||
|
||||
onClick={() => popupController.setMemberInformationPopup(false)}
|
||||
onClick={() => popupController.setZipCodePopup(false)}
|
||||
```
|
||||
|
||||
### useEffect 정리
|
||||
# useEffect 정리
|
||||
|
||||
- client url pathname 변경시 -> @/components/ui/Header.tsx
|
||||
|
||||
### User Role 구분
|
||||
# User Role 구분
|
||||
|
||||
session에 있는 role 키로 구분한다
|
||||
|
||||
@ -123,12 +56,12 @@ session에 있는 role 키로 구분한다
|
||||
session.role === 'Builder'
|
||||
- teshg44 / 1234 -> 시공사\
|
||||
session.role === 'Builder'
|
||||
- isogai@yanegiken.co.jp / password -> Q.Partners 계정\
|
||||
- partners -> Q.Partners 계정\
|
||||
session.role === 'Partner'
|
||||
- 이외의 경우 -> 굳이 체크할 필요 없어보임\
|
||||
session.role === 'User'
|
||||
|
||||
### 지붕재 적합성 TODO
|
||||
# 지붕재 적합성 TODO
|
||||
|
||||
```
|
||||
const suitableCheckIcon = (value: string): string => {
|
||||
@ -149,21 +82,3 @@ const suitableCheckMemo = (value: string): string => {
|
||||
|
||||
- src/hooks/useSuitable.ts > suitableCheckIcon(), suitableCheckMemo()
|
||||
- 추후 지붕재 적합성 데이터 CUD 구현 시 ×, ー 데이터 관리 필요
|
||||
|
||||
# 주의
|
||||
|
||||
## <span style="color: red">Prisma ORM 사용 시 주의사항</span>
|
||||
|
||||
현재 프로젝트는 Prisma ORM을 통해 데이터베이스의 일부 테이블만 관리하고 있습니다. 이로 인해 `prisma db pull` 또는 `prisma db push` 명령어 사용 시 주의가 필요합니다.
|
||||
|
||||
### <span style="color: red">잠재적 위험성</span>
|
||||
|
||||
- `schema.prisma` 파일에 정의된 모델이 의도치 않게 수정될 수 있습니다.
|
||||
- 기존에 정의된 모델의 속성이나 관계가 손상될 수 있습니다.
|
||||
- 데이터베이스 스키마와 Prisma 스키마 간의 불일치가 발생할 수 있습니다.
|
||||
|
||||
### <span style="color: red">권장 사항</span>
|
||||
|
||||
- 데이터베이스 스키마 변경이 필요한 경우, 반드시 팀 내 논의 후 진행하시기 바랍니다.
|
||||
- `prisma db pull` 실행 전 현재 `schema.prisma` 파일의 백업을 권장합니다.
|
||||
- 변경 사항 적용 전 staging 환경에서 충분한 테스트를 진행하시기 바랍니다.
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'on-site-survey',
|
||||
script: 'node_modules/next/dist/bin/next',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -1,71 +0,0 @@
|
||||
# 루트 레이아웃 구조
|
||||
|
||||
이 문서는 애플리케이션의 모든 페이지에서 공통으로 사용되는 루트 레이아웃(`src/app/layout.tsx`)의 구조를 설명합니다.
|
||||
|
||||
## 컴포넌트 계층 구조
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[RootLayout] --> B[ReactQueryProviders]
|
||||
B --> C[EdgeProvider]
|
||||
C --> D[HTML Structure]
|
||||
|
||||
D --> E[Header]
|
||||
D --> F[Children/Main Content]
|
||||
D --> G[Footer]
|
||||
D --> H[Float Button]
|
||||
D --> I[PopupController]
|
||||
|
||||
J[Session Management] --> |Session Data| C
|
||||
|
||||
subgraph Providers
|
||||
B
|
||||
C
|
||||
end
|
||||
|
||||
subgraph Layout Components
|
||||
E
|
||||
F
|
||||
G
|
||||
H
|
||||
I
|
||||
end
|
||||
|
||||
subgraph Session
|
||||
J
|
||||
end
|
||||
```
|
||||
|
||||
## 주요 컴포넌트
|
||||
|
||||
### 프로바이더
|
||||
|
||||
- **ReactQueryProviders**: React Query 상태 관리를 위한 최상위 프로바이더
|
||||
- **EdgeProvider**: 세션 데이터를 관리하고 애플리케이션에 제공
|
||||
|
||||
### 레이아웃 컴포넌트
|
||||
|
||||
- **Header**: 공통 헤더 컴포넌트
|
||||
- **Children**: 메인 콘텐츠 영역 (페이지별 콘텐츠)
|
||||
- **Footer**: 공통 푸터 컴포넌트
|
||||
- **Float Button**: 플로팅 액션 버튼
|
||||
- **PopupController**: 팝업/모달 상태 관리
|
||||
|
||||
### 세션 관리
|
||||
|
||||
- `iron-session`을 사용한 서버 사이드 세션 관리
|
||||
- 세션 데이터는 EdgeProvider를 통해 전역적으로 접근 가능
|
||||
|
||||
## 데이터 흐름
|
||||
|
||||
1. 세션 데이터는 서버 사이드에서 관리됨
|
||||
2. 세션 데이터는 EdgeProvider로 전달됨
|
||||
3. 모든 레이아웃 컴포넌트는 세션 컨텍스트에 접근 가능
|
||||
4. React Query는 데이터 페칭과 캐싱 기능을 제공
|
||||
|
||||
## 구현 세부사항
|
||||
|
||||
- 서버 컴포넌트로 구현
|
||||
- Next.js App Router 구조 사용
|
||||
- 서버 사이드 렌더링 지원
|
||||
- 하이드레이션 억제를 통한 성능 최적화
|
||||
@ -6,7 +6,6 @@ const nextConfig: NextConfig = {
|
||||
sassOptions: {
|
||||
includePaths: [path.join(__dirname, './src/styles')],
|
||||
},
|
||||
serverExternalPackages: ['@react-pdf/renderer'],
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
|
||||
544
package-lock.json
generated
544
package-lock.json
generated
@ -9,12 +9,10 @@
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.7.0",
|
||||
"@react-pdf/renderer": "^4.3.0",
|
||||
"@tanstack/react-query": "^5.71.0",
|
||||
"@tanstack/react-query-devtools": "^5.71.0",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"axios": "^1.8.4",
|
||||
"crypto-js": "^4.2.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"iron-session": "^8.0.4",
|
||||
"lucide": "^0.503.0",
|
||||
@ -22,7 +20,6 @@
|
||||
"mysql2": "^3.14.1",
|
||||
"next": "15.2.4",
|
||||
"nodemailer": "^7.0.3",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-to-pdf": "^2.0.0",
|
||||
@ -33,7 +30,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/mysql": "^2.15.27",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
@ -1556,24 +1552,6 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@pdf-lib/standard-fonts": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",
|
||||
"integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pako": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@pdf-lib/upng": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz",
|
||||
"integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pako": "^1.0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/client": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.7.0.tgz",
|
||||
@ -1657,180 +1635,6 @@
|
||||
"@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": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
@ -2142,13 +1946,6 @@
|
||||
"integrity": "sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/crypto-js": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz",
|
||||
"integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/mysql": {
|
||||
"version": "2.15.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.27.tgz",
|
||||
@ -2233,12 +2030,6 @@
|
||||
"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": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
|
||||
@ -2315,15 +2106,6 @@
|
||||
],
|
||||
"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": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-6.1.0.tgz",
|
||||
@ -2349,24 +2131,6 @@
|
||||
"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": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
|
||||
@ -2516,15 +2280,6 @@
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||
"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": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
@ -2556,13 +2311,15 @@
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
@ -2639,12 +2396,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-line-break": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||
@ -2662,9 +2413,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
@ -2746,12 +2497,6 @@
|
||||
"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": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz",
|
||||
@ -2785,12 +2530,6 @@
|
||||
"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": {
|
||||
"version": "5.18.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
||||
@ -2947,12 +2686,6 @@
|
||||
"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": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
||||
@ -2992,23 +2725,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||
@ -3152,21 +2868,6 @@
|
||||
"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": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||
@ -3206,12 +2907,6 @@
|
||||
"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": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
@ -3284,7 +2979,8 @@
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/is-docker": {
|
||||
"version": "3.0.0",
|
||||
@ -3358,12 +3054,6 @@
|
||||
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
|
||||
"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": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
|
||||
@ -3385,15 +3075,6 @@
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"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": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
|
||||
@ -3410,12 +3091,6 @@
|
||||
"integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==",
|
||||
"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": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
|
||||
@ -3737,25 +3412,6 @@
|
||||
"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": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
@ -3810,18 +3466,6 @@
|
||||
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||
"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": {
|
||||
"version": "7.18.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
|
||||
@ -3861,12 +3505,6 @@
|
||||
"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": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
@ -4082,24 +3720,6 @@
|
||||
"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": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz",
|
||||
@ -4118,18 +3738,6 @@
|
||||
"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": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
@ -4139,24 +3747,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pdf-lib": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz",
|
||||
"integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pdf-lib/standard-fonts": "^1.0.0",
|
||||
"@pdf-lib/upng": "^1.0.1",
|
||||
"pako": "^1.0.11",
|
||||
"tslib": "^1.11.1"
|
||||
}
|
||||
},
|
||||
"node_modules/pdf-lib/node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
@ -4212,12 +3802,6 @@
|
||||
"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": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.7.0.tgz",
|
||||
@ -4256,32 +3840,12 @@
|
||||
"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": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"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": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
|
||||
@ -4313,12 +3877,6 @@
|
||||
"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": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-to-pdf/-/react-to-pdf-2.0.0.tgz",
|
||||
@ -4368,21 +3926,6 @@
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||
"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": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
||||
@ -4450,9 +3993,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
@ -4532,6 +4075,7 @@
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
@ -4640,12 +4184,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
|
||||
@ -4731,12 +4269,6 @@
|
||||
"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": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@ -4782,32 +4314,6 @@
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"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": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.1.tgz",
|
||||
@ -4823,12 +4329,6 @@
|
||||
"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": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
|
||||
@ -4847,26 +4347,6 @@
|
||||
"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": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz",
|
||||
|
||||
@ -16,12 +16,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.7.0",
|
||||
"@react-pdf/renderer": "^4.3.0",
|
||||
"@tanstack/react-query": "^5.71.0",
|
||||
"@tanstack/react-query-devtools": "^5.71.0",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"axios": "^1.8.4",
|
||||
"crypto-js": "^4.2.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"iron-session": "^8.0.4",
|
||||
"lucide": "^0.503.0",
|
||||
@ -29,7 +27,6 @@
|
||||
"mysql2": "^3.14.1",
|
||||
"next": "15.2.4",
|
||||
"nodemailer": "^7.0.3",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-to-pdf": "^2.0.0",
|
||||
@ -40,7 +37,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/mysql": "^2.15.27",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
|
||||
@ -7,67 +7,103 @@ datasource db {
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model MS_SUITABLE {
|
||||
id Int @id @default(autoincrement())
|
||||
product_name String @db.VarChar(200)
|
||||
manufacturer String? @db.VarChar(200)
|
||||
roof_material String? @db.VarChar(100)
|
||||
shape String? @db.VarChar(200)
|
||||
support_roof_tile String? @db.VarChar(2)
|
||||
support_roof_tile_memo String? @db.VarChar(500)
|
||||
support_roof_bracket String? @db.VarChar(200)
|
||||
support_roof_bracket_memo String? @db.VarChar(500)
|
||||
yg_anchor String? @db.VarChar(200)
|
||||
yg_anchor_memo String? @db.VarChar(500)
|
||||
rg_roof_tile_part String? @db.VarChar(200)
|
||||
rg_roof_tile_part_memo String? @db.VarChar(500)
|
||||
dido_hunt_support_tile_2 String? @db.VarChar(200)
|
||||
dido_hunt_support_tile_2_memo String? @db.VarChar(500)
|
||||
takashima_power_base String? @db.VarChar(200)
|
||||
takashima_power_base_memo String? @db.VarChar(500)
|
||||
takashima_tile_bracket String? @db.VarChar(200)
|
||||
takashima_tile_bracket_memo String? @db.VarChar(500)
|
||||
slate_bracket_4 String? @db.VarChar(200)
|
||||
slate_bracket_4_memo String? @db.VarChar(500)
|
||||
slate_single_metal_bracket String? @db.VarChar(200)
|
||||
slate_single_metal_bracket_memo String? @db.VarChar(500)
|
||||
dido_hunt_short_rack_4 String? @db.VarChar(200)
|
||||
dido_hunt_short_rack_4_memo String? @db.VarChar(500)
|
||||
takashima_slate_bracket_slate_single String? @db.VarChar(200)
|
||||
takashima_slate_bracket_slate_single_memo String? @db.VarChar(500)
|
||||
df_metal_bracket String? @db.VarChar(200)
|
||||
df_metal_bracket_memo String? @db.VarChar(500)
|
||||
slate_metal_bracket String? @db.VarChar(200)
|
||||
slate_metal_bracket_memo String? @db.VarChar(500)
|
||||
takashima_slate_bracket_metal_roof String? @db.VarChar(200)
|
||||
takashima_slate_bracket_metal_roof_memo String? @db.VarChar(500)
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
}
|
||||
|
||||
model SD_SURVEY_SALES_BASIC_INFO {
|
||||
ID Int @id @default(autoincrement())
|
||||
SRL_NO String @db.NVarChar(20)
|
||||
REPRESENTATIVE String @db.NVarChar(200)
|
||||
STORE String? @db.NVarChar(200)
|
||||
CONSTRUCTION_POINT String? @db.NVarChar(200)
|
||||
CONSTRUCTION_POINT_ID String? @db.NVarChar(200)
|
||||
INVESTIGATION_DATE String? @db.NVarChar(10)
|
||||
BUILDING_NAME String? @db.NVarChar(200)
|
||||
CUSTOMER_NAME String? @db.NVarChar(200)
|
||||
POST_CODE String? @db.NVarChar(10)
|
||||
ADDRESS String? @db.NVarChar(200)
|
||||
ADDRESS_DETAIL String? @db.NVarChar(300)
|
||||
SUBMISSION_STATUS Boolean @default(false)
|
||||
SUBMISSION_DATE DateTime? @db.Date
|
||||
SUBMISSION_TARGET_ID String? @db.NVarChar(200)
|
||||
SUBMISSION_TARGET_NM String? @db.NVarChar(200)
|
||||
REG_DT DateTime @default(now())
|
||||
UPT_DT DateTime @updatedAt
|
||||
REPRESENTATIVE_ID String? @db.NVarChar(100)
|
||||
STORE_ID String? @db.NVarChar(100)
|
||||
DETAIL_INFO SD_SURVEY_SALES_DETAIL_INFO?
|
||||
ID Int @id @default(autoincrement())
|
||||
SRL_NO String @db.VarChar(20)
|
||||
REPRESENTATIVE String @db.VarChar(200)
|
||||
STORE String? @db.VarChar(200)
|
||||
CONSTRUCTION_POINT String? @db.VarChar(200)
|
||||
INVESTIGATION_DATE String? @db.VarChar(10)
|
||||
BUILDING_NAME String? @db.VarChar(200)
|
||||
CUSTOMER_NAME String? @db.VarChar(200)
|
||||
POST_CODE String? @db.VarChar(10)
|
||||
ADDRESS String? @db.VarChar(200)
|
||||
ADDRESS_DETAIL String? @db.VarChar(300)
|
||||
SUBMISSION_STATUS Boolean @default(false)
|
||||
SUBMISSION_DATE DateTime? @db.Date
|
||||
SUBMISSION_TARGET_ID String? @db.VarChar(200)
|
||||
REG_DT DateTime @default(now())
|
||||
UPT_DT DateTime @updatedAt
|
||||
REPRESENTATIVE_ID String? @db.VarChar(100)
|
||||
STORE_ID String? @db.VarChar(100)
|
||||
DETAIL_INFO SD_SURVEY_SALES_DETAIL_INFO?
|
||||
}
|
||||
|
||||
model SD_SURVEY_SALES_DETAIL_INFO {
|
||||
ID Int @id @default(autoincrement())
|
||||
CONTRACT_CAPACITY String? @db.NVarChar(20)
|
||||
RETAIL_COMPANY String? @db.NVarChar(100)
|
||||
SUPPLEMENTARY_FACILITIES String? @db.NVarChar(20)
|
||||
SUPPLEMENTARY_FACILITIES_ETC String? @db.NVarChar(200)
|
||||
INSTALLATION_SYSTEM String? @db.NVarChar(20)
|
||||
INSTALLATION_SYSTEM_ETC String? @db.NVarChar(200)
|
||||
CONSTRUCTION_YEAR String? @db.NVarChar(200)
|
||||
CONSTRUCTION_YEAR_ETC String? @db.NVarChar(200)
|
||||
ROOF_MATERIAL String? @db.NVarChar(20)
|
||||
ROOF_MATERIAL_ETC String? @db.NVarChar(200)
|
||||
ROOF_SHAPE String? @db.NVarChar(20)
|
||||
ROOF_SHAPE_ETC String? @db.NVarChar(200)
|
||||
ROOF_SLOPE String? @db.NVarChar(5)
|
||||
HOUSE_STRUCTURE String? @db.NVarChar(20)
|
||||
HOUSE_STRUCTURE_ETC String? @db.NVarChar(200)
|
||||
RAFTER_MATERIAL String? @db.NVarChar(20)
|
||||
RAFTER_MATERIAL_ETC String? @db.NVarChar(200)
|
||||
RAFTER_SIZE String? @db.NVarChar(20)
|
||||
RAFTER_SIZE_ETC String? @db.NVarChar(200)
|
||||
RAFTER_PITCH String? @db.NVarChar(20)
|
||||
RAFTER_PITCH_ETC String? @db.NVarChar(200)
|
||||
RAFTER_DIRECTION String? @db.NVarChar(20)
|
||||
OPEN_FIELD_PLATE_KIND String? @db.NVarChar(20)
|
||||
OPEN_FIELD_PLATE_KIND_ETC String? @db.NVarChar(200)
|
||||
OPEN_FIELD_PLATE_THICKNESS String? @db.NVarChar(5)
|
||||
CONTRACT_CAPACITY String? @db.VarChar(20)
|
||||
RETAIL_COMPANY String? @db.VarChar(100)
|
||||
SUPPLEMENTARY_FACILITIES String? @db.VarChar(20)
|
||||
SUPPLEMENTARY_FACILITIES_ETC String? @db.VarChar(200)
|
||||
INSTALLATION_SYSTEM String? @db.VarChar(20)
|
||||
INSTALLATION_SYSTEM_ETC String? @db.VarChar(200)
|
||||
CONSTRUCTION_YEAR String? @db.VarChar(200)
|
||||
CONSTRUCTION_YEAR_ETC String? @db.VarChar(200)
|
||||
ROOF_MATERIAL String? @db.VarChar(20)
|
||||
ROOF_MATERIAL_ETC String? @db.VarChar(200)
|
||||
ROOF_SHAPE String? @db.VarChar(20)
|
||||
ROOF_SHAPE_ETC String? @db.VarChar(200)
|
||||
ROOF_SLOPE String? @db.VarChar(5)
|
||||
HOUSE_STRUCTURE String? @db.VarChar(20)
|
||||
HOUSE_STRUCTURE_ETC String? @db.VarChar(200)
|
||||
RAFTER_MATERIAL String? @db.VarChar(20)
|
||||
RAFTER_MATERIAL_ETC String? @db.VarChar(200)
|
||||
RAFTER_SIZE String? @db.VarChar(20)
|
||||
RAFTER_SIZE_ETC String? @db.VarChar(200)
|
||||
RAFTER_PITCH String? @db.VarChar(20)
|
||||
RAFTER_PITCH_ETC String? @db.VarChar(200)
|
||||
RAFTER_DIRECTION String? @db.VarChar(20)
|
||||
OPEN_FIELD_PLATE_KIND String? @db.VarChar(20)
|
||||
OPEN_FIELD_PLATE_KIND_ETC String? @db.VarChar(200)
|
||||
OPEN_FIELD_PLATE_THICKNESS String? @db.VarChar(5)
|
||||
LEAK_TRACE Boolean? @default(false)
|
||||
WATERPROOF_MATERIAL String? @db.NVarChar(20)
|
||||
WATERPROOF_MATERIAL_ETC String? @db.NVarChar(200)
|
||||
INSULATION_PRESENCE String? @db.NVarChar(20)
|
||||
INSULATION_PRESENCE_ETC String? @db.NVarChar(200)
|
||||
STRUCTURE_ORDER String? @db.NVarChar(20)
|
||||
STRUCTURE_ORDER_ETC String? @db.NVarChar(200)
|
||||
INSTALLATION_AVAILABILITY String? @db.NVarChar(20)
|
||||
INSTALLATION_AVAILABILITY_ETC String? @db.NVarChar(200)
|
||||
MEMO String? @db.NVarChar(500)
|
||||
WATERPROOF_MATERIAL String? @db.VarChar(20)
|
||||
WATERPROOF_MATERIAL_ETC String? @db.VarChar(200)
|
||||
INSULATION_PRESENCE String? @db.VarChar(20)
|
||||
INSULATION_PRESENCE_ETC String? @db.VarChar(200)
|
||||
STRUCTURE_ORDER String? @db.VarChar(20)
|
||||
STRUCTURE_ORDER_ETC String? @db.VarChar(200)
|
||||
INSTALLATION_AVAILABILITY String? @db.VarChar(20)
|
||||
INSTALLATION_AVAILABILITY_ETC String? @db.VarChar(200)
|
||||
MEMO String? @db.VarChar(500)
|
||||
REG_DT DateTime @default(now())
|
||||
UPT_DT DateTime @updatedAt
|
||||
BASIC_INFO_ID Int @unique
|
||||
@ -135,20 +171,20 @@ model BC_COMM_L {
|
||||
}
|
||||
|
||||
model MS_SUITABLE_ROOF_MATERIAL_GROUP {
|
||||
ID Int @id @default(autoincrement())
|
||||
ROOF_MATL_GRP_CD String @db.NVarChar(200)
|
||||
ROOF_MT_CD String @db.NVarChar(200)
|
||||
REG_DT DateTime @default(now(), map: "DF__MS_SUITAB__creat__4F7CD00D")
|
||||
UPT_DT DateTime?
|
||||
ID Int @id @default(autoincrement())
|
||||
ROOF_MATERIAL_GROUP String @db.VarChar(200)
|
||||
ROOF_MT_CD String @db.VarChar(200)
|
||||
REG_DT DateTime @default(now(), map: "DF__MS_SUITAB__creat__4F7CD00D")
|
||||
UPT_DT DateTime?
|
||||
}
|
||||
|
||||
model MS_SUITABLE_DETAIL {
|
||||
ID Int @id @default(autoincrement())
|
||||
MAIN_ID Int
|
||||
TRESTLE_MFPC_CD String? @db.NVarChar(200)
|
||||
TRESTLE_MANUFACTURER_PRODUCT_NAME String? @db.NVarChar(200)
|
||||
MEMO String? @db.NVarChar(500)
|
||||
REG_DT DateTime @default(now(), map: "DF__MS_SUITAB__REG_D__1332DBDC")
|
||||
TRESTLE_MFPC_CD String? @db.VarChar(200)
|
||||
TRESTLE_MANUFACTURER_PRODUCT_NAME String? @db.VarChar(200)
|
||||
MEMO String? @db.VarChar(500)
|
||||
REG_DT DateTime @default(now(), map: "DF__MS_SUITAB__creat__571DF1D5")
|
||||
UPT_DT DateTime?
|
||||
MS_SUITABLE_MAIN MS_SUITABLE_MAIN @relation(fields: [MAIN_ID], references: [ID], onUpdate: NoAction, map: "MS_SUITABLE_DETAIL_MS_SUITABLE_MAIN_FK")
|
||||
|
||||
@ -157,25 +193,24 @@ model MS_SUITABLE_DETAIL {
|
||||
|
||||
model MS_SUITABLE_MAIN {
|
||||
ID Int @id @default(autoincrement())
|
||||
PRODUCT_NAME String @db.NVarChar(200)
|
||||
MANU_FT_CD String? @db.NVarChar(200)
|
||||
ROOF_MT_CD String? @db.NVarChar(100)
|
||||
ROOF_SH_CD String? @db.NVarChar(200)
|
||||
REG_DT DateTime @default(now(), map: "DF__MS_SUITAB__REG_D__10566F31")
|
||||
PRODUCT_NAME String @db.VarChar(200)
|
||||
MANU_FT_CD String? @db.VarChar(200)
|
||||
ROOF_MT_CD String? @db.VarChar(100)
|
||||
ROOF_SH_CD String? @db.VarChar(200)
|
||||
REG_DT DateTime @default(now(), map: "DF__MS_SUITAB__creat__5441852A")
|
||||
UPT_DT DateTime?
|
||||
UPT_ID String? @db.NVarChar(50)
|
||||
DEL_YN String? @db.NVarChar(1)
|
||||
MS_SUITABLE_DETAIL MS_SUITABLE_DETAIL[]
|
||||
|
||||
@@index([PRODUCT_NAME], map: "MS_SUITABLE_MAIN_PRODUCT_NAME_IDX")
|
||||
@@index([ROOF_MT_CD, PRODUCT_NAME], map: "MS_SUITABLE_MAIN_ROOF_MT_CD_IDX")
|
||||
}
|
||||
|
||||
/// The underlying table does not contain a valid unique identifier and can therefore currently not be handled by Prisma Client.
|
||||
model MS_USR_TRK {
|
||||
ID Int @id @default(autoincrement())
|
||||
OWNER String @db.NVarChar(100)
|
||||
TYPE String @db.NVarChar(50)
|
||||
URL String? @db.NVarChar(200)
|
||||
OWNER String @db.VarChar(100)
|
||||
TYPE String @db.VarChar(50)
|
||||
URL String? @db.VarChar(200)
|
||||
REG_DT DateTime @default(now())
|
||||
DATA String? @db.NVarChar(200)
|
||||
DATA String? @db.VarChar(200)
|
||||
}
|
||||
|
||||
@ -56,9 +56,7 @@ export async function POST(request: Request) {
|
||||
session.groupId = result.data.data.groupId
|
||||
session.storeLvl = result.data.data.storeLvl
|
||||
session.custCd = result.data.data.custCd
|
||||
session.builderId = result.data.data.builderId
|
||||
session.builderNo = result.data.data.builderNo
|
||||
session.builderNm = result.data.data.builderNm
|
||||
session.isLoggedIn = true
|
||||
|
||||
if (result.data.data.userId === 'T01') {
|
||||
@ -105,9 +103,7 @@ export async function POST(request: Request) {
|
||||
GROUP_ID: result.data.data.groupId,
|
||||
STORE_LVL: result.data.data.storeLvl,
|
||||
CUST_CD: result.data.data.custCd,
|
||||
BUILDER_ID: result.data.data.builderId,
|
||||
BUILDER_NO: result.data.data.builderNo,
|
||||
BUILDER_NM: result.data.data.builderNm,
|
||||
IS_LOGGED_IN: true,
|
||||
ROLE: '',
|
||||
}
|
||||
|
||||
@ -84,9 +84,7 @@ export async function POST(request: Request) {
|
||||
session.groupId = null
|
||||
session.storeLvl = null
|
||||
session.custCd = null
|
||||
session.builderId = data[0].user_seko_id
|
||||
session.builderNo = data[0].user_seko_id
|
||||
session.builderNm = data[0].supplier_name
|
||||
session.isLoggedIn = true
|
||||
session.role = 'Partner'
|
||||
|
||||
@ -124,9 +122,7 @@ export async function POST(request: Request) {
|
||||
GROUP_ID: null,
|
||||
STORE_LVL: null,
|
||||
CUST_CD: null,
|
||||
BUILDER_ID: data[0].user_seko_id,
|
||||
BUILDER_NO: data[0].user_seko_id,
|
||||
BUILDER_NM: data[0].supplier_name,
|
||||
IS_LOGGED_IN: true,
|
||||
ROLE: 'Partner',
|
||||
}
|
||||
|
||||
@ -1,73 +1,33 @@
|
||||
import axios from 'axios'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
// export async function GET(request: Request) {
|
||||
// const { searchParams } = new URL(request.url)
|
||||
// const encodeFileNo = searchParams.get('encodeFileNo')
|
||||
// const srcFileNm = searchParams.get('srcFileNm')
|
||||
|
||||
// if (!encodeFileNo) {
|
||||
// return NextResponse.json({ error: 'encodeFileNo is required' }, { status: 400 })
|
||||
// }
|
||||
|
||||
// try {
|
||||
// const response = await axios.get(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/file/downloadFile2`, {
|
||||
// params: {
|
||||
// encodeFileNo,
|
||||
// },
|
||||
// responseType: 'arraybuffer',
|
||||
// })
|
||||
|
||||
// if (response.headers['content-type'] === 'text/html;charset=utf-8') {
|
||||
// return NextResponse.json({ error: 'file not found' }, { status: 404 })
|
||||
// }
|
||||
|
||||
// const contentType = response.headers['content-type'] || 'application/octet-stream'
|
||||
// const contentDisposition = response.headers['content-disposition'] || 'inline'
|
||||
|
||||
// return new NextResponse(response.data, {
|
||||
// status: 200,
|
||||
// headers: {
|
||||
// 'Content-Type': contentType,
|
||||
// 'Content-Disposition': contentDisposition,
|
||||
// },
|
||||
// })
|
||||
// } catch (error: any) {
|
||||
// console.error('File download error:', error)
|
||||
// return NextResponse.json({ error: error.response?.data || 'Failed to download file' }, { status: 500 })
|
||||
// }
|
||||
// }
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const encodeFileNo = searchParams.get('encodeFileNo')
|
||||
const srcFileNm = searchParams.get('srcFileNm') || 'downloaded-file'
|
||||
|
||||
const srcFileNm = searchParams.get('srcFileNm')
|
||||
|
||||
if (!encodeFileNo) {
|
||||
return NextResponse.json({ error: 'encodeFileNo is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const url = `${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/file/downloadFile2?encodeFileNo=${encodeFileNo}`
|
||||
|
||||
try {
|
||||
const resp = await fetch(url)
|
||||
|
||||
if (!resp.ok) {
|
||||
return NextResponse.json({ error: 'Failed to download file' }, { status: 500 })
|
||||
const response = await axios.get(`${process.env.NEXT_PUBLIC_INQUIRY_API_URL}/api/file/downloadFile2`, {
|
||||
responseType: 'arraybuffer',
|
||||
params: {
|
||||
encodeFileNo,
|
||||
},
|
||||
})
|
||||
if (response.headers['content-type'] === 'text/html;charset=utf-8') {
|
||||
return NextResponse.json({ error: 'file not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
const contentType = resp.headers.get('content-type') || 'application/octet-stream'
|
||||
const contentDisposition = resp.headers.get('content-disposition') || `attachment; filename="${srcFileNm}"`
|
||||
|
||||
return new NextResponse(resp.body, {
|
||||
return new NextResponse(response.data, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': contentType,
|
||||
'Content-Disposition': contentDisposition,
|
||||
'Content-Type': 'application/octet-stream;charset=UTF-8',
|
||||
'Content-Disposition': `attachment; filename="${srcFileNm}"`,
|
||||
},
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error('File download error:', error)
|
||||
return NextResponse.json({ error: error.response?.data || 'Failed to download file' }, { status: 500 })
|
||||
return NextResponse.json({ error: error.response.data }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,117 +0,0 @@
|
||||
import React from 'react'
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { pdf, Document } from '@react-pdf/renderer'
|
||||
import { PDFDocument } from 'pdf-lib'
|
||||
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 생성 : mainId 100개씩 청크로 나누기
|
||||
const CHUNK_SIZE = 100
|
||||
const pdfBuffers: Uint8Array[] = []
|
||||
for (let i = 0; i < suitable.length; i += CHUNK_SIZE) {
|
||||
const chunk = suitable.slice(i, i + CHUNK_SIZE)
|
||||
const content = React.createElement(
|
||||
Document,
|
||||
null,
|
||||
React.createElement(SuitablePdf, {
|
||||
data: chunk,
|
||||
fileTitle: fileTitle,
|
||||
firstPage: i === 0,
|
||||
}),
|
||||
)
|
||||
const pdfBlob = await pdf(content).toBlob()
|
||||
const arrayBuffer = await pdfBlob.arrayBuffer()
|
||||
pdfBuffers.push(new Uint8Array(arrayBuffer))
|
||||
}
|
||||
|
||||
// 모든 PDF 버퍼 병합
|
||||
const mergedPdfBytes = await mergePdfBuffers(pdfBuffers)
|
||||
|
||||
const fileName = `${fileTitle.replace(' ', '_')}.pdf`
|
||||
const encodedFileName = encodeURIComponent(fileName)
|
||||
|
||||
return new NextResponse(Buffer.from(mergedPdfBytes), {
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
async function mergePdfBuffers(buffers: Uint8Array[]) {
|
||||
const mergedPdf = await PDFDocument.create()
|
||||
|
||||
for (const buf of buffers) {
|
||||
const pdf = await PDFDocument.load(buf)
|
||||
const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices())
|
||||
copiedPages.forEach((page) => mergedPdf.addPage(page))
|
||||
}
|
||||
|
||||
const mergedPdfBytes = await mergedPdf.save()
|
||||
return mergedPdfBytes
|
||||
}
|
||||
@ -2,96 +2,20 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { prisma } from '@/libs/prisma'
|
||||
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
|
||||
builderId: 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, builderId: string | null): boolean => {
|
||||
if (!builderId) return false
|
||||
return survey.CONSTRUCTION_POINT_ID === builderId
|
||||
}
|
||||
|
||||
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.builderId)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
try {
|
||||
const { id } = await params
|
||||
const { searchParams } = new URL(request.url)
|
||||
|
||||
const sessionParams: SessionParams = {
|
||||
role: searchParams.get('role'),
|
||||
storeId: searchParams.get('storeId'),
|
||||
builderId: searchParams.get('builderId'),
|
||||
isLoggedIn: searchParams.get('isLoggedIn'),
|
||||
}
|
||||
// @ts-ignore
|
||||
const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.findFirst({
|
||||
where: {
|
||||
ID: Number(id),
|
||||
},
|
||||
const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.findUnique({
|
||||
where: { ID: Number(id) },
|
||||
include: {
|
||||
DETAIL_INFO: true,
|
||||
},
|
||||
})
|
||||
if (checkRole(survey, sessionParams)) {
|
||||
return NextResponse.json(survey)
|
||||
} else {
|
||||
return NextResponse.json({ error: '該当物件の照会権限がありません。' }, { status: 403 })
|
||||
}
|
||||
} catch (error: any) {
|
||||
return NextResponse.json(survey)
|
||||
} catch (error) {
|
||||
console.error('Error fetching survey:', error)
|
||||
return NextResponse.json({ error: 'データの取得に失敗しました。' }, { status: 500 })
|
||||
return NextResponse.json({ error: 'Failed to fetch survey' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,18 +115,20 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
|
||||
try {
|
||||
const { id } = await params
|
||||
const body = await request.json()
|
||||
// @ts-ignore
|
||||
const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({
|
||||
where: { ID: Number(id) },
|
||||
data: {
|
||||
SUBMISSION_STATUS: true,
|
||||
SUBMISSION_DATE: new Date(),
|
||||
SUBMISSION_TARGET_ID: body.targetId,
|
||||
SUBMISSION_TARGET_NM: body.targetNm,
|
||||
UPT_DT: new Date(),
|
||||
},
|
||||
})
|
||||
return NextResponse.json({ message: 'Survey confirmed successfully', data: survey })
|
||||
|
||||
if (body.targetId) {
|
||||
// @ts-ignore
|
||||
const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({
|
||||
where: { ID: Number(id) },
|
||||
data: {
|
||||
SUBMISSION_STATUS: true,
|
||||
SUBMISSION_DATE: new Date(),
|
||||
SUBMISSION_TARGET_ID: body.targetId,
|
||||
UPT_DT: new Date(),
|
||||
},
|
||||
})
|
||||
return NextResponse.json({ message: 'Survey confirmed successfully', data: survey })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating survey:', error)
|
||||
return NextResponse.json({ error: 'Failed to update survey' }, { status: 500 })
|
||||
|
||||
@ -11,8 +11,8 @@ type SearchParams = {
|
||||
sort?: string | null // 정렬 방식
|
||||
offset?: string | null
|
||||
role?: string | null // 회원권한한
|
||||
storeId?: string | null // 판매점ID
|
||||
builderId?: string | null // 시공ID
|
||||
store?: string | null // 판매점ID
|
||||
builderNo?: string | null // 시공ID
|
||||
}
|
||||
|
||||
type WhereCondition = {
|
||||
@ -25,10 +25,8 @@ type WhereCondition = {
|
||||
const SEARCH_OPTIONS = [
|
||||
'BUILDING_NAME', // 건물명
|
||||
'REPRESENTATIVE', // 담당자
|
||||
'STORE', // 판매점명
|
||||
'STORE_ID', // 판매점ID
|
||||
'CONSTRUCTION_POINT', // 시공점명
|
||||
'CONSTRUCTION_POINT_ID', // 시공점ID
|
||||
'STORE', // 판매점
|
||||
'CONSTRUCTION_POINT', // 시공점
|
||||
'CUSTOMER_NAME', // 고객명
|
||||
'POST_CODE', // 우편번호
|
||||
'ADDRESS', // 주소
|
||||
@ -77,11 +75,11 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => {
|
||||
where.OR = [
|
||||
{
|
||||
// 같은 판매점에서 작성한 제출/제출되지 않은 매물
|
||||
AND: [{ STORE_ID: { equals: params.storeId } }],
|
||||
AND: [{ STORE_ID: { equals: params.store } }],
|
||||
},
|
||||
{
|
||||
// MUSUBI (시공권한 X) 가 ORDER 에 제출한 매물
|
||||
AND: [{ SUBMISSION_TARGET_ID: { equals: params.storeId } }, { SUBMISSION_STATUS: { equals: true } }],
|
||||
AND: [{ SUBMISSION_TARGET_ID: { equals: params.store } }, { SUBMISSION_STATUS: { equals: true } }],
|
||||
},
|
||||
]
|
||||
break
|
||||
@ -90,14 +88,19 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => {
|
||||
where.OR = [
|
||||
{
|
||||
// MUSUBI (시공권한 X) 같은 판매점에서 작성한 제출/제출되지 않은 매물
|
||||
AND: [{ STORE_ID: { equals: params.storeId } }, { CONSTRUCTION_POINT_ID: { equals: params.builderId } }],
|
||||
AND: [
|
||||
{ STORE_ID: { equals: params.store } },
|
||||
{
|
||||
OR: [{ CONSTRUCTION_POINT: { equals: null } }, { CONSTRUCTION_POINT: { equals: '' } }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// MUSUBI (시공권한 O) 가 MUSUBI 에 제출한 매물 + PARTNER 가 제출한 매물
|
||||
AND: [
|
||||
{ SUBMISSION_TARGET_ID: { equals: params.storeId } },
|
||||
{ CONSTRUCTION_POINT_ID: { not: null } },
|
||||
{ CONSTRUCTION_POINT_ID: { not: '' } },
|
||||
{ SUBMISSION_TARGET_ID: { equals: params.store } },
|
||||
{ CONSTRUCTION_POINT: { not: null } },
|
||||
{ CONSTRUCTION_POINT: { not: '' } },
|
||||
{ SUBMISSION_STATUS: { equals: true } },
|
||||
],
|
||||
},
|
||||
@ -106,9 +109,10 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => {
|
||||
|
||||
case 'Builder': // MUSUBI (시공권한 O)
|
||||
case 'Partner': // PARTNER
|
||||
// 시공ID 같은 매물
|
||||
// 시공점이 있고 STORE_ID가 시공ID와 같은 매물
|
||||
where.AND?.push({
|
||||
CONSTRUCTION_POINT_ID: { equals: params.builderId },
|
||||
CONSTRUCTION_POINT: { not: null },
|
||||
STORE_ID: { equals: params.builderNo },
|
||||
})
|
||||
break
|
||||
|
||||
@ -123,7 +127,7 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => {
|
||||
},
|
||||
{
|
||||
STORE_ID: {
|
||||
equals: params.storeId,
|
||||
equals: params.store,
|
||||
},
|
||||
},
|
||||
]
|
||||
@ -146,12 +150,12 @@ export async function GET(request: Request) {
|
||||
const params: SearchParams = {
|
||||
keyword: searchParams.get('keyword'),
|
||||
searchOption: searchParams.get('searchOption'),
|
||||
isMySurvey: searchParams.get('isMySurvey'),
|
||||
isMySurvey: searchParams.get('isMySurvey'), //representativeId
|
||||
sort: searchParams.get('sort'),
|
||||
offset: searchParams.get('offset'),
|
||||
role: searchParams.get('role'),
|
||||
storeId: searchParams.get('storeId'),
|
||||
builderId: searchParams.get('builderId'),
|
||||
store: searchParams.get('store'), //storeId
|
||||
builderNo: searchParams.get('builderNo'),
|
||||
}
|
||||
|
||||
// 검색 조건 구성
|
||||
@ -172,6 +176,7 @@ export async function GET(request: Request) {
|
||||
if (Object.keys(roleCondition).length > 0) {
|
||||
where.AND.push(roleCondition)
|
||||
}
|
||||
|
||||
// 페이지네이션 데이터 조회
|
||||
//@ts-ignore
|
||||
const surveys = await prisma.SD_SURVEY_SALES_BASIC_INFO.findMany({
|
||||
|
||||
@ -38,7 +38,7 @@ export default function Detail() {
|
||||
<tr>
|
||||
<th>顧客名</th>
|
||||
<td>
|
||||
{session?.userNm} {session?.builderNm ? `[${session?.builderNm}]` : ''}
|
||||
{session?.userNm} {session?.builderNo ? `[${session?.builderNo}]` : ''}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@ -5,7 +5,7 @@ import { useState } from 'react'
|
||||
|
||||
export default function ListForm() {
|
||||
const router = useRouter()
|
||||
const { inquiryListRequest, setInquiryListRequest, reset, setOffset } = useInquiryFilterStore()
|
||||
const { inquiryListRequest, setInquiryListRequest, reset } = useInquiryFilterStore()
|
||||
const [searchKeyword, setSearchKeyword] = useState(inquiryListRequest.schTitle ?? '')
|
||||
|
||||
const handleSearch = () => {
|
||||
@ -40,19 +40,6 @@ export default function ListForm() {
|
||||
onChange={(e) => setSearchKeyword(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
{searchKeyword && (
|
||||
<button
|
||||
className="del-icon"
|
||||
onClick={() => {
|
||||
setSearchKeyword('')
|
||||
setInquiryListRequest({
|
||||
...inquiryListRequest,
|
||||
schTitle: '',
|
||||
})
|
||||
setOffset(1)
|
||||
}}
|
||||
></button>
|
||||
)}
|
||||
<button className="search-icon" onClick={handleSearch}></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -86,7 +86,7 @@ export default function ListTable() {
|
||||
<div className="inquiry-table-filter">
|
||||
<div className="filter-check">
|
||||
<div className="check-form-box">
|
||||
<input type="checkbox" id="ch01" onChange={handleMyInquiry} checked={inquiryListRequest.schRegId === session.userId} />
|
||||
<input type="checkbox" id="ch01" onChange={handleMyInquiry} />
|
||||
<label htmlFor="ch01">私が書いたお問い合わせ</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Binary file not shown.
@ -1,21 +1,12 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import generatePDF, { Margin, Options, Resolution, usePDF } from 'react-to-pdf'
|
||||
import { useSuitable } from '@/hooks/useSuitable'
|
||||
import { useSuitableStore } from '@/store/useSuitableStore'
|
||||
import { SUITABLE_HEAD_CODE, type Suitable, type SuitableDetail } from '@/types/Suitable'
|
||||
import { useRef } from 'react'
|
||||
import generatePDF, { Margin, Resolution } from 'react-to-pdf'
|
||||
|
||||
export default function SuitableDownloadPdf() {
|
||||
const [fileName, setFileName] = useState<string[]>([])
|
||||
const [createTime, setCreateTime] = useState('')
|
||||
const targetRef = useRef<HTMLDivElement>(null)
|
||||
const { toCodeName, toSuitableDetail, selectedSuitables, isSelectedSuitablesLoading } = useSuitable()
|
||||
const { selectedCategory, suitableCommCode, selectedItemsSearching, setSelectedItemsSearching } = useSuitableStore()
|
||||
|
||||
const handleDownPdf = () => {
|
||||
const options: Options = {
|
||||
filename: `${fileName.join('_')}.pdf`,
|
||||
const options = {
|
||||
method: 'open' as const,
|
||||
resolution: Resolution.HIGH,
|
||||
page: {
|
||||
@ -40,41 +31,14 @@ export default function SuitableDownloadPdf() {
|
||||
generatePDF(targetRef, options)
|
||||
// generatePDF(targetRef, { filename: 'page.pdf' })
|
||||
}
|
||||
|
||||
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')}`
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setCreateTime(formatDateString())
|
||||
setFileName([
|
||||
`(${suitableCommCode.get(SUITABLE_HEAD_CODE.ROOF_MATL_GRP_CD)?.find((category) => category.code === selectedCategory)?.codeJp})`,
|
||||
'屋根材適合表',
|
||||
])
|
||||
if (!selectedItemsSearching) {
|
||||
setSelectedItemsSearching(true)
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (!selectedCategory) return <div>잘못된 접근입니다.</div>
|
||||
if (isSelectedSuitablesLoading) return <div>Loading...</div>
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <button onClick={() => handleDownPdf()}>PDFダウンロード</button> */}
|
||||
<div className="pdf-table-wrap" ref={targetRef}>
|
||||
<div className="pdf-intro-page">
|
||||
<div className="pdf-intro-tit-wrap">
|
||||
<div className="pdf-intro-tit mb20">ハンファジャパン株式会社</div>
|
||||
<div className="pdf-intro-tit mb20">{fileName.join(' ')}</div>
|
||||
<div className="pdf-intro-date">{createTime}</div>
|
||||
<div className="pdf-intro-tit mb20">(瓦) 屋根材適合表</div>
|
||||
<div className="pdf-intro-date">2025年4月30日 10:40</div>
|
||||
</div>
|
||||
<div className="pdf-intro-cont-wrap">
|
||||
<p>本適合表は参考資料としてご使用下さい。</p>
|
||||
@ -82,49 +46,640 @@ export default function SuitableDownloadPdf() {
|
||||
<p>又、現場環境(働き、勾配、瓦桟木条件等)により本適合表と異なる適合結果となる場合が御座います。予めご了承下さい。</p>
|
||||
<p>屋根材以外の設置条件(垂木、野地板等の設置基準)も必ずご確認下さい。</p>
|
||||
</div>
|
||||
<div className="pdf-intro-foot-date">{createTime}</div>
|
||||
</div>
|
||||
<div className="pdf-table-content">
|
||||
<div className="pdf-table-grid-wrap">
|
||||
{selectedSuitables?.map((item: Suitable) => (
|
||||
<div className="pdf-table-card" key={item.id}>
|
||||
<div className="pdf-table-tit-wrap">
|
||||
<span>{item.productName}</span>
|
||||
<span>{toCodeName(SUITABLE_HEAD_CODE.MANU_FT_CD, item.manuFtCd)}</span>
|
||||
<span>{toCodeName(SUITABLE_HEAD_CODE.ROOF_MT_CD, item.roofMtCd)}</span>
|
||||
</div>
|
||||
<div className="pdf-roof-table">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col width={'18%'} />
|
||||
<col width={'23%'} />
|
||||
<col width={'18%'} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>金具タイプ</th>
|
||||
<th>金具名</th>
|
||||
<th>設置可否</th>
|
||||
<th>備考</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{toSuitableDetail(item.detail).map((subItem: SuitableDetail) => (
|
||||
<tr key={subItem.id}>
|
||||
<td>{toCodeName(SUITABLE_HEAD_CODE.ROOF_SH_CD, item.roofShCd)}</td>
|
||||
<td>{toCodeName(SUITABLE_HEAD_CODE.TRESTLE_MFPC_CD, subItem.trestleMfpcCd)}</td>
|
||||
<td>{subItem.trestleManufacturerProductName}</td>
|
||||
<td>{subItem.memo}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="pdf-table-card">
|
||||
<div className="pdf-table-tit-wrap">
|
||||
<span>屋根材製品名</span>
|
||||
<span>メーカー名</span>
|
||||
<span>屋根材の種類</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="pdf-roof-table">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col width={'18%'} />
|
||||
<col width={'23%'} />
|
||||
<col width={'18%'} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>金具タイプ</th>
|
||||
<th>金具名</th>
|
||||
<th>設置可否</th>
|
||||
<th>備考</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pdf-table-card">
|
||||
<div className="pdf-table-tit-wrap">
|
||||
<span>屋根材製品名</span>
|
||||
<span>メーカー名</span>
|
||||
<span>屋根材の種類</span>
|
||||
</div>
|
||||
<div className="pdf-roof-table">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col width={'18%'} />
|
||||
<col width={'23%'} />
|
||||
<col width={'18%'} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>金具タイプ</th>
|
||||
<th>金具名</th>
|
||||
<th>設置可否</th>
|
||||
<th>備考</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pdf-table-card">
|
||||
<div className="pdf-table-tit-wrap">
|
||||
<span>屋根材製品名</span>
|
||||
<span>メーカー名</span>
|
||||
<span>屋根材の種類</span>
|
||||
</div>
|
||||
<div className="pdf-roof-table">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col width={'18%'} />
|
||||
<col width={'23%'} />
|
||||
<col width={'18%'} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>金具タイプ</th>
|
||||
<th>金具名</th>
|
||||
<th>設置可否</th>
|
||||
<th>備考</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pdf-table-card">
|
||||
<div className="pdf-table-tit-wrap">
|
||||
<span>屋根材製品名</span>
|
||||
<span>メーカー名</span>
|
||||
<span>屋根材の種類</span>
|
||||
</div>
|
||||
<div className="pdf-roof-table">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col width={'18%'} />
|
||||
<col width={'23%'} />
|
||||
<col width={'18%'} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>金具タイプ</th>
|
||||
<th>金具名</th>
|
||||
<th>設置可否</th>
|
||||
<th>備考</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pdf-table-card">
|
||||
<div className="pdf-table-tit-wrap">
|
||||
<span>屋根材製品名</span>
|
||||
<span>メーカー名</span>
|
||||
<span>屋根材の種類</span>
|
||||
</div>
|
||||
<div className="pdf-roof-table">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col width={'18%'} />
|
||||
<col width={'23%'} />
|
||||
<col width={'18%'} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>金具タイプ</th>
|
||||
<th>金具名</th>
|
||||
<th>設置可否</th>
|
||||
<th>備考</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pdf-table-card">
|
||||
<div className="pdf-table-tit-wrap">
|
||||
<span>屋根材製品名</span>
|
||||
<span>メーカー名</span>
|
||||
<span>屋根材の種類</span>
|
||||
</div>
|
||||
<div className="pdf-roof-table">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col width={'18%'} />
|
||||
<col width={'23%'} />
|
||||
<col width={'18%'} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>金具タイプ</th>
|
||||
<th>金具名</th>
|
||||
<th>設置可否</th>
|
||||
<th>備考</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pdf-table-card">
|
||||
<div className="pdf-table-tit-wrap">
|
||||
<span>屋根材製品名</span>
|
||||
<span>メーカー名</span>
|
||||
<span>屋根材の種類</span>
|
||||
</div>
|
||||
<div className="pdf-roof-table">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col width={'18%'} />
|
||||
<col width={'23%'} />
|
||||
<col width={'18%'} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>金具タイプ</th>
|
||||
<th>金具名</th>
|
||||
<th>設置可否</th>
|
||||
<th>備考</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pdf-table-card">
|
||||
<div className="pdf-table-tit-wrap">
|
||||
<span>屋根材製品名</span>
|
||||
<span>メーカー名</span>
|
||||
<span>屋根材の種類</span>
|
||||
</div>
|
||||
<div className="pdf-roof-table">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col width={'18%'} />
|
||||
<col width={'23%'} />
|
||||
<col width={'18%'} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>金具タイプ</th>
|
||||
<th>金具名</th>
|
||||
<th>設置可否</th>
|
||||
<th>備考</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pdf-table-card">
|
||||
<div className="pdf-table-tit-wrap">
|
||||
<span>屋根材製品名</span>
|
||||
<span>メーカー名</span>
|
||||
<span>屋根材の種類</span>
|
||||
</div>
|
||||
<div className="pdf-roof-table">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col width={'18%'} />
|
||||
<col width={'23%'} />
|
||||
<col width={'18%'} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>金具タイプ</th>
|
||||
<th>金具名</th>
|
||||
<th>設置可否</th>
|
||||
<th>備考</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pdf-table-card">
|
||||
<div className="pdf-table-tit-wrap">
|
||||
<span>屋根材製品名</span>
|
||||
<span>メーカー名</span>
|
||||
<span>屋根材の種類</span>
|
||||
</div>
|
||||
<div className="pdf-roof-table">
|
||||
<table>
|
||||
<colgroup>
|
||||
<col width={'18%'} />
|
||||
<col width={'23%'} />
|
||||
<col width={'18%'} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>金具タイプ</th>
|
||||
<th>金具名</th>
|
||||
<th>設置可否</th>
|
||||
<th>備考</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>木ねじ打ち込み式</td>
|
||||
<td>屋根技研支持瓦</td>
|
||||
<td>C で設置可</td>
|
||||
<td>支持瓦はアンダーラップの先端を削って納める(Try-U40はこの瓦の前身)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pdf-intro-foot-date">{createTime}</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -1,133 +0,0 @@
|
||||
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, firstPage }: { data: Suitable[]; fileTitle: string; firstPage: boolean }) {
|
||||
const createTime = formatDateString()
|
||||
|
||||
return (
|
||||
<Page size="A4" orientation="landscape" style={styles.page}>
|
||||
{/* Intro Section */}
|
||||
{firstPage && (
|
||||
<View>
|
||||
<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>
|
||||
)}
|
||||
|
||||
<View>
|
||||
{/* Cards Section */}
|
||||
{data?.map((item: Suitable) => (
|
||||
<View key={item.id}>
|
||||
{/* Table Title */}
|
||||
<View>
|
||||
<Text style={styles.text}>{item.productName}</Text>
|
||||
<Text style={styles.text}>{item.manuFtCd}</Text>
|
||||
<Text style={styles.text}>{item.roofMtCd}</Text>
|
||||
</View>
|
||||
|
||||
{/* Table */}
|
||||
<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 */}
|
||||
<View>
|
||||
{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>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* Footer */}
|
||||
<View style={styles.footer} fixed>
|
||||
<Text style={[styles.footerDate, styles.text]}>{createTime}</Text>
|
||||
</View>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
@ -19,13 +19,13 @@ export default function SurveySaleDownloadPdf() {
|
||||
const isGeneratedRef = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
setIsShow(true)
|
||||
if (isLoadingSurveyDetail || !surveyDetail || isGeneratedRef.current) return
|
||||
isGeneratedRef.current = true
|
||||
handleDownPdf()
|
||||
}, [surveyDetail?.id, isLoadingSurveyDetail])
|
||||
|
||||
const handleDownPdf = () => {
|
||||
setIsShow(true)
|
||||
const options = {
|
||||
method: 'open' as const,
|
||||
resolution: Resolution.HIGH,
|
||||
@ -50,10 +50,12 @@ export default function SurveySaleDownloadPdf() {
|
||||
|
||||
generatePDF(targetRef, options).then(() => {
|
||||
setIsShow(false)
|
||||
router.replace(`/survey-sale/${id}`)
|
||||
alert('PDFの生成が完了しました。 ポップアップウィンドウからダウンロードしてください。')
|
||||
router.push(`/survey-sale/${id}`)
|
||||
})
|
||||
}
|
||||
const supplementList = supplementaryFacilities
|
||||
.filter((facility) => surveyDetail?.detailInfo?.supplementaryFacilities?.includes(facility.id.toString()))
|
||||
.map((facility) => facility.name)
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -247,15 +249,11 @@ export default function SurveySaleDownloadPdf() {
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
>
|
||||
{surveyDetail?.detailInfo?.supplementaryFacilities
|
||||
? supplementaryFacilities
|
||||
.filter((facility) => surveyDetail?.detailInfo?.supplementaryFacilities?.includes(facility.id.toString()))
|
||||
.map((facility) => facility.name)
|
||||
.join(', ') +
|
||||
(surveyDetail?.detailInfo?.supplementaryFacilitiesEtc ? `, ${surveyDetail?.detailInfo?.supplementaryFacilitiesEtc}` : '')
|
||||
{supplementList === null && surveyDetail?.detailInfo?.supplementaryFacilitiesEtc === null
|
||||
? '-'
|
||||
: surveyDetail?.detailInfo?.supplementaryFacilitiesEtc
|
||||
? `${surveyDetail?.detailInfo?.supplementaryFacilitiesEtc}`
|
||||
: '-'}
|
||||
? `${supplementList.join(', ')}, ${surveyDetail?.detailInfo?.supplementaryFacilitiesEtc}`
|
||||
: supplementList.join(', ')}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -332,11 +330,7 @@ export default function SurveySaleDownloadPdf() {
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
>
|
||||
{surveyDetail?.detailInfo?.constructionYear === '1'
|
||||
? '新築'
|
||||
: surveyDetail?.detailInfo?.constructionYearEtc
|
||||
? `既築 (${surveyDetail?.detailInfo?.constructionYear}年)`
|
||||
: '-'}
|
||||
{surveyDetail?.detailInfo?.constructionYear === '1' ? '新築' : `既築 (${surveyDetail?.detailInfo?.constructionYear}年)`}
|
||||
</td>
|
||||
<th
|
||||
style={{
|
||||
|
||||
@ -2,18 +2,17 @@
|
||||
|
||||
import Image from 'next/image'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { usePopupController } from '@/store/popupController'
|
||||
import SuitableDetailPopupButton from './SuitableDetailPopupButton'
|
||||
import { useSuitable } from '@/hooks/useSuitable'
|
||||
import { usePopupController } from '@/store/popupController'
|
||||
import { useSuitableStore } from '@/store/useSuitableStore'
|
||||
import { SUITABLE_HEAD_CODE, type Suitable, type SuitableDetail } from '@/types/Suitable'
|
||||
|
||||
export default function SuitableDetailPopup() {
|
||||
const popupController = usePopupController()
|
||||
const { toCodeName, toSuitableDetail, suitableCheckIcon, suitableCheckMemo, selectedSuitables, isSelectedSuitablesLoading } = useSuitable()
|
||||
const { setSelectedItemsSearching } = useSuitableStore()
|
||||
const { getSelectedItemsData, toCodeName, toSuitableDetail, suitableCheckIcon, suitableCheckMemo } = useSuitable()
|
||||
|
||||
const [openItems, setOpenItems] = useState<Set<number>>(new Set())
|
||||
const [suitableDetails, setSuitableDetails] = useState<Suitable[]>([])
|
||||
|
||||
// 아이템 열기/닫기
|
||||
const toggleItemOpen = useCallback((itemId: number) => {
|
||||
@ -25,7 +24,8 @@ export default function SuitableDetailPopup() {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedItemsSearching(true)
|
||||
// TODO: 로딩 처리 필요
|
||||
getSelectedItemsData().then((data) => setSuitableDetails(data))
|
||||
}, [])
|
||||
|
||||
return (
|
||||
@ -45,60 +45,56 @@ export default function SuitableDetailPopup() {
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="compliance-check-pop-wrap">
|
||||
{isSelectedSuitablesLoading ? (
|
||||
<div>Loading...</div>
|
||||
) : (
|
||||
selectedSuitables?.map((item: Suitable) => (
|
||||
<div className={`compliance-check-bx ${openItems.has(item.id) ? 'act' : ''}`} key={item.id}>
|
||||
<div className="check-name-wrap">
|
||||
<div className="check-name">{item.productName}</div>
|
||||
<div className="check-name-btn">
|
||||
<button className="bx-btn" onClick={() => toggleItemOpen(item.id)}></button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="compliance-check-pop-contents">
|
||||
<div className="check-pop-data-wrap">
|
||||
<div className="check-pop-data-tit">屋根技研 支持瓦</div>
|
||||
<div className="check-pop-data-txt">{toCodeName(SUITABLE_HEAD_CODE.MANU_FT_CD, item.manuFtCd)}</div>
|
||||
</div>
|
||||
<div className="check-pop-data-wrap">
|
||||
<div className="check-pop-data-tit">屋根材</div>
|
||||
<div className="check-pop-data-txt">{toCodeName(SUITABLE_HEAD_CODE.ROOF_MT_CD, item.roofMtCd)}</div>
|
||||
</div>
|
||||
<div className="check-pop-data-wrap">
|
||||
<div className="check-pop-data-tit">金具タイプ</div>
|
||||
<div className="check-pop-data-txt">{toCodeName(SUITABLE_HEAD_CODE.ROOF_SH_CD, item.roofShCd)}</div>
|
||||
</div>
|
||||
<div className="check-pop-data-table-wrap">
|
||||
{toSuitableDetail(item.detail).map((subItem: SuitableDetail) => (
|
||||
<div className="check-pop-data-table" key={subItem.id}>
|
||||
<div className="pop-data-table-head">
|
||||
<div className="pop-data-table-head-name">{toCodeName(SUITABLE_HEAD_CODE.TRESTLE_MFPC_CD, subItem.trestleMfpcCd)}</div>
|
||||
<div className="pop-data-table-head-icon">
|
||||
<div className="compliance-icon">
|
||||
<Image src={suitableCheckIcon(subItem.trestleManufacturerProductName)} width={22} height={22} alt="" />
|
||||
</div>
|
||||
{subItem.memo && (
|
||||
<div className="compliance-icon">
|
||||
<Image src={'/assets/images/sub/compliance_tip_icon.svg'} width={22} height={22} alt=""></Image>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="pop-data-table-body">{suitableCheckMemo(subItem.trestleManufacturerProductName)}</div>
|
||||
{subItem.memo && (
|
||||
<div className="pop-data-table-footer">
|
||||
<div className="pop-data-table-footer-unit">備考</div>
|
||||
<div className="pop-data-table-footer-data">{subItem.memo}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{suitableDetails.map((item: Suitable) => (
|
||||
<div className={`compliance-check-bx ${openItems.has(item.id) ? 'act' : ''}`} key={item.id}>
|
||||
<div className="check-name-wrap">
|
||||
<div className="check-name">{item.productName}</div>
|
||||
<div className="check-name-btn">
|
||||
<button className="bx-btn" onClick={() => toggleItemOpen(item.id)}></button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
<div className="compliance-check-pop-contents">
|
||||
<div className="check-pop-data-wrap">
|
||||
<div className="check-pop-data-tit">屋根技研 支持瓦</div>
|
||||
<div className="check-pop-data-txt">{toCodeName(SUITABLE_HEAD_CODE.MANU_FT_CD, item.manuFtCd)}</div>
|
||||
</div>
|
||||
<div className="check-pop-data-wrap">
|
||||
<div className="check-pop-data-tit">屋根材</div>
|
||||
<div className="check-pop-data-txt">{toCodeName(SUITABLE_HEAD_CODE.ROOF_MT_CD, item.roofMtCd)}</div>
|
||||
</div>
|
||||
<div className="check-pop-data-wrap">
|
||||
<div className="check-pop-data-tit">金具タイプ</div>
|
||||
<div className="check-pop-data-txt">{toCodeName(SUITABLE_HEAD_CODE.ROOF_SH_CD, item.roofShCd)}</div>
|
||||
</div>
|
||||
<div className="check-pop-data-table-wrap">
|
||||
{toSuitableDetail(item.detail).map((subItem: SuitableDetail) => (
|
||||
<div className="check-pop-data-table" key={subItem.id}>
|
||||
<div className="pop-data-table-head">
|
||||
<div className="pop-data-table-head-name">{toCodeName(SUITABLE_HEAD_CODE.TRESTLE_MFPC_CD, subItem.trestleMfpcCd)}</div>
|
||||
<div className="pop-data-table-head-icon">
|
||||
<div className="compliance-icon">
|
||||
<Image src={suitableCheckIcon(subItem.trestleManufacturerProductName)} width={22} height={22} alt="" />
|
||||
</div>
|
||||
{subItem.memo && (
|
||||
<div className="compliance-icon">
|
||||
<Image src={'/assets/images/sub/compliance_tip_icon.svg'} width={22} height={22} alt=""></Image>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="pop-data-table-body">{suitableCheckMemo(subItem.trestleManufacturerProductName)}</div>
|
||||
{subItem.memo && (
|
||||
<div className="pop-data-table-footer">
|
||||
<div className="pop-data-table-footer-unit">備考</div>
|
||||
<div className="pop-data-table-footer-data">{subItem.memo}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<SuitableDetailPopupButton />
|
||||
</div>
|
||||
|
||||
@ -2,36 +2,31 @@
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { usePopupController } from '@/store/popupController'
|
||||
import { useSuitable } from '@/hooks/useSuitable'
|
||||
|
||||
export default function SuitableDetailPopupButton() {
|
||||
const router = useRouter()
|
||||
const popupController = usePopupController()
|
||||
const { downloadSuitablePdf } = useSuitable()
|
||||
|
||||
const handleClosePopup = () => {
|
||||
popupController.setSuitableDetailPopup(false)
|
||||
}
|
||||
|
||||
const handleRedirectPage = (path: string) => {
|
||||
handleClosePopup()
|
||||
router.push(path)
|
||||
}
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div className="btn-flex-wrap com">
|
||||
<div className="btn-bx">
|
||||
<button className="btn-frame n-blue icon" onClick={handleClosePopup}>
|
||||
<button className="btn-frame n-blue icon" onClick={() => popupController.setSuitableDetailPopup(false)}>
|
||||
閉じる<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="btn-bx">
|
||||
<button className="btn-frame red icon" onClick={downloadSuitablePdf}>
|
||||
<button className="btn-frame red icon">
|
||||
ウンロード<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="btn-bx">
|
||||
<button className="btn-frame n-blue icon" onClick={() => handleRedirectPage('/inquiry/regist')}>
|
||||
<button
|
||||
className="btn-frame n-blue icon"
|
||||
onClick={async () => {
|
||||
await popupController.setSuitableDetailPopup(false)
|
||||
router.push('/inquiry/regist')
|
||||
}}
|
||||
>
|
||||
1:1お問い合わせ<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -2,7 +2,7 @@ import Image from 'next/image'
|
||||
import { usePopupController } from '@/store/popupController'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { useSurvey } from '@/hooks/useSurvey'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useSessionStore } from '@/store/session'
|
||||
import { useCommCode } from '@/hooks/useCommCode'
|
||||
import { CommCode } from '@/types/CommCode'
|
||||
@ -11,13 +11,12 @@ import { useSpinnerStore } from '@/store/spinnerStore'
|
||||
|
||||
interface SubmitFormData {
|
||||
saleBase: string | null
|
||||
targetId: string | null
|
||||
targetNm: string | null
|
||||
store: string
|
||||
sender: string
|
||||
receiver: string[] | string
|
||||
reference: string | null
|
||||
title: string
|
||||
contents: string | null
|
||||
contents: string
|
||||
}
|
||||
|
||||
interface FormField {
|
||||
@ -33,47 +32,38 @@ export default function SurveySaleSubmitPopup() {
|
||||
const routeId = params.id
|
||||
|
||||
const { setIsShow } = useSpinnerStore()
|
||||
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[]>([])
|
||||
|
||||
const { getCommCode } = useCommCode()
|
||||
|
||||
useEffect(() => {
|
||||
if (!session?.isLoggedIn || !surveyDetail?.id) return
|
||||
if (session?.role === 'Admin') {
|
||||
if (session?.isLoggedIn && session?.role === 'Admin') {
|
||||
getCommCode('SALES_OFFICE_CD').then((codes) => {
|
||||
setCommCodeList(codes)
|
||||
})
|
||||
}
|
||||
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])
|
||||
}, [session])
|
||||
|
||||
const FORM_FIELDS: FormField[] = [
|
||||
{ id: 'sender', name: '発送者', required: true },
|
||||
{ id: 'saleBase', name: '提出地点選択', required: session?.role === 'Admin' },
|
||||
{ id: 'targetNm', name: '提出販売店', required: session?.role !== 'Admin' },
|
||||
{ id: 'store', name: '提出販売店', required: true },
|
||||
{ id: 'sender', name: '発送者', required: true },
|
||||
{ id: 'receiver', name: '受信者', required: true },
|
||||
{ id: 'reference', name: '参考', required: false },
|
||||
{ id: 'title', name: 'タイトル', required: true },
|
||||
{ id: 'contents', name: '内容', required: false },
|
||||
{ id: 'contents', name: '内容', required: true },
|
||||
]
|
||||
|
||||
const [submitData, setSubmitData] = useState<SubmitFormData>({
|
||||
saleBase: null,
|
||||
store: '',
|
||||
sender: session?.email ?? '',
|
||||
receiver: [],
|
||||
reference: null,
|
||||
title: '[HANASYS現地調査] 調査物件が提出.',
|
||||
contents: '',
|
||||
})
|
||||
|
||||
const { submitSurvey, isSubmittingSurvey } = useSurvey(Number(routeId))
|
||||
|
||||
const handleInputChange = (field: keyof SubmitFormData, value: string) => {
|
||||
@ -84,7 +74,7 @@ export default function SurveySaleSubmitPopup() {
|
||||
const requiredFields = FORM_FIELDS.filter((field) => field.required)
|
||||
|
||||
for (const field of requiredFields) {
|
||||
if (data[field.id] === '' || data[field.id] === null || data[field.id]?.length === 0) {
|
||||
if (data[field.id]?.length === 0) {
|
||||
alert(`${field.name}は必須入力項目です。`)
|
||||
const element = document.getElementById(field.id)
|
||||
if (element) {
|
||||
@ -96,38 +86,32 @@ export default function SurveySaleSubmitPopup() {
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO: Admin_Sub 계정 매핑된 submit target id 추가!!!! && 메일 테스트트
|
||||
const handleSubmit = () => {
|
||||
if (validateData(submitData)) {
|
||||
window.neoConfirm('送信しますか? 送信後は変更・修正することはできません。', () => {
|
||||
setIsShow(true)
|
||||
submitSurvey({ targetId: submitData.store })
|
||||
sendEmail({
|
||||
to: submitData.receiver,
|
||||
subject: submitData.title,
|
||||
content: contentsRef.current?.innerHTML ?? '',
|
||||
content: submitData.contents,
|
||||
})
|
||||
.then(() => {
|
||||
if (!isSubmittingSurvey) {
|
||||
alert('提出が完了しました。')
|
||||
// submitSurvey({ targetId: submitData.targetId, targetNm: submitData.targetNm })
|
||||
popupController.setSurveySaleSubmitPopup(false)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error sending email:', error)
|
||||
alert('メール送信に失敗しました。 再度送信してください。')
|
||||
if (!isSubmittingSurvey) {
|
||||
popupController.setSurveySaleSubmitPopup(false)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error sending email:', error)
|
||||
alert('メール送信に失敗しました。')
|
||||
})
|
||||
.finally(() => {
|
||||
submitSurvey({ targetId: submitData.targetId, targetNm: submitData.targetNm })
|
||||
setIsShow(false)
|
||||
popupController.setSurveySaleSubmitPopup(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const contentsRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const handleClose = () => {
|
||||
popupController.setSurveySaleSubmitPopup(false)
|
||||
}
|
||||
@ -138,9 +122,6 @@ export default function SurveySaleSubmitPopup() {
|
||||
if (field.id === 'saleBase' && session?.role !== 'Admin') {
|
||||
return null
|
||||
}
|
||||
if (field.id === 'targetNm' && session?.role === 'Admin') {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="data-input-form-bx" key={field.id}>
|
||||
@ -149,38 +130,12 @@ export default function SurveySaleSubmitPopup() {
|
||||
</div>
|
||||
<div className="data-input">
|
||||
{field.id === 'contents' ? (
|
||||
<div className="submit-content" id={field.id}>
|
||||
<div ref={contentsRef}>
|
||||
<p style={{ fontSize: '13px', fontWeight: '400', color: '#2e3a59', marginBottom: '15px' }}>
|
||||
HANASYS現地調査アプリを使用した現地調査結果が送信されました。
|
||||
</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>
|
||||
<textarea
|
||||
className="textarea-form"
|
||||
id={field.id}
|
||||
value={submitData[field.id] ?? ''}
|
||||
onChange={(e) => handleInputChange(field.id, e.target.value)}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{field.id === 'saleBase' && session?.role === 'Admin' ? (
|
||||
|
||||
@ -107,13 +107,6 @@ export default function ZipCodePopup() {
|
||||
<td>{item.address3}</td>
|
||||
</tr>
|
||||
))}
|
||||
{addressInfo?.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={3} className="al-c">
|
||||
<div className="nodata">조회된 데이터가 없습니다.</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@ -1,13 +1,23 @@
|
||||
'use client'
|
||||
|
||||
import Image from 'next/image'
|
||||
import { useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import SuitableList from './SuitableList'
|
||||
import SuitableSearch from './SuitableSearch'
|
||||
import { useSuitable } from '@/hooks/useSuitable'
|
||||
|
||||
export default function Suitable() {
|
||||
const [reference, setReference] = useState(true)
|
||||
|
||||
const { getSuitableCommCode, clearSuitableSearch } = useSuitable()
|
||||
|
||||
useEffect(() => {
|
||||
getSuitableCommCode()
|
||||
return () => {
|
||||
clearSuitableSearch({ items: true, category: true, keyword: true })
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="border-frame">
|
||||
<SuitableSearch />
|
||||
|
||||
@ -6,7 +6,7 @@ import { useSuitableStore } from '@/store/useSuitableStore'
|
||||
|
||||
export default function SuitableButton() {
|
||||
const popupController = usePopupController()
|
||||
const { getSuitableIds, clearSuitableStore, downloadSuitablePdf } = useSuitable()
|
||||
const { getSuitableIds, clearSuitableSearch } = useSuitable()
|
||||
const { selectedItems, addAllSelectedItem } = useSuitableStore()
|
||||
|
||||
const handleSelectAll = async () => {
|
||||
@ -21,14 +21,6 @@ export default function SuitableButton() {
|
||||
popupController.setSuitableDetailPopup(true)
|
||||
}
|
||||
|
||||
const handleRedirectPdfDownload = () => {
|
||||
if (selectedItems.size === 0) {
|
||||
alert('屋根材を選択してください。')
|
||||
return
|
||||
}
|
||||
downloadSuitablePdf()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="float-btn-wrap">
|
||||
<div className="btn-flex-wrap com">
|
||||
@ -38,7 +30,7 @@ export default function SuitableButton() {
|
||||
全選択<i className="btn-arr"></i>
|
||||
</button>
|
||||
) : (
|
||||
<button className="btn-frame n-blue icon" onClick={() => clearSuitableStore({ items: true })}>
|
||||
<button className="btn-frame n-blue icon" onClick={() => clearSuitableSearch({ items: true })}>
|
||||
全て解除<i className="btn-arr"></i>
|
||||
</button>
|
||||
)}
|
||||
@ -49,7 +41,7 @@ export default function SuitableButton() {
|
||||
</button>
|
||||
</div>
|
||||
<div className="btn-bx">
|
||||
<button className="btn-frame n-blue icon" onClick={handleRedirectPdfDownload}>
|
||||
<button className="btn-frame n-blue icon">
|
||||
選択ダウンロード<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -20,7 +20,7 @@ export default function SuitableList() {
|
||||
isLoading,
|
||||
suitableCheckIcon,
|
||||
} = useSuitable()
|
||||
const { selectedItems, addSelectedItem, removeSelectedItem, setSelectedItemsSearching } = useSuitableStore()
|
||||
const { selectedItems, addSelectedItem, removeSelectedItem } = useSuitableStore()
|
||||
const [openItems, setOpenItems] = useState<Set<number>>(new Set())
|
||||
const observerTarget = useRef<HTMLDivElement>(null)
|
||||
|
||||
@ -48,7 +48,6 @@ export default function SuitableList() {
|
||||
// 아이템 클릭
|
||||
const handleItemClick = useCallback(
|
||||
(mainId: number, detailId?: number, detailIds?: Set<number>): void => {
|
||||
setSelectedItemsSearching(false)
|
||||
isItemSelected(mainId, detailId) ? removeSelectedItem(mainId, detailId) : addSelectedItem(mainId, detailId, detailIds)
|
||||
},
|
||||
[isItemSelected, addSelectedItem, removeSelectedItem],
|
||||
|
||||
@ -9,7 +9,7 @@ import { SUITABLE_HEAD_CODE } from '@/types/Suitable'
|
||||
export default function SuitableSearch() {
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
|
||||
const { getSuitableCommCode, clearSuitableStore } = useSuitable()
|
||||
const { getSuitableCommCode, clearSuitableSearch } = useSuitable()
|
||||
const { suitableCommCode, selectedCategory, setSelectedCategory, setSearchKeyword } = useSuitableStore()
|
||||
|
||||
const handleInputChange = (value: string) => {
|
||||
@ -31,12 +31,14 @@ export default function SuitableSearch() {
|
||||
|
||||
const handleInputClear = () => {
|
||||
setSearchValue('')
|
||||
clearSuitableStore({ items: true, keyword: true })
|
||||
clearSuitableSearch({ items: true, keyword: true })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
clearSuitableStore({ items: true, category: true, keyword: true })
|
||||
getSuitableCommCode()
|
||||
return () => {
|
||||
clearSuitableSearch({ items: true, category: true, keyword: true })
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
@ -44,7 +46,7 @@ export default function SuitableSearch() {
|
||||
<div className="sale-form-bx">
|
||||
<select className="select-form" name="" id="" value={selectedCategory || ''} onChange={(e) => setSelectedCategory(e.target.value)}>
|
||||
<option value="">屋根材を選択してください.</option>
|
||||
{suitableCommCode.get(SUITABLE_HEAD_CODE.ROOF_MATL_GRP_CD)?.map((category: CommCode, index: number) => (
|
||||
{suitableCommCode.get(SUITABLE_HEAD_CODE.ROOF_MATERIAL_GROUP)?.map((category: CommCode, index: number) => (
|
||||
<option key={index} value={category.code}>
|
||||
{category.codeJp}
|
||||
</option>
|
||||
|
||||
@ -4,38 +4,46 @@ import { useEffect, useState } from 'react'
|
||||
import { useSurveySaleTabState } from '@/store/surveySaleTabState'
|
||||
import type { SurveyBasicRequest } from '@/types/Survey'
|
||||
import type { Mode } from 'fs'
|
||||
import { useSessionStore } from '@/store/session'
|
||||
import { usePopupController } from '@/store/popupController'
|
||||
import { useAddressStore } from '@/store/addressStore'
|
||||
import { SessionData } from '@/types/Auth'
|
||||
|
||||
interface BasicFormProps {
|
||||
basicInfo: SurveyBasicRequest
|
||||
setBasicInfo: (basicInfo: SurveyBasicRequest) => void
|
||||
mode: Mode
|
||||
session: SessionData
|
||||
}
|
||||
|
||||
export default function BasicForm({ basicInfo, setBasicInfo, mode, session }: BasicFormProps) {
|
||||
export default function BasicForm(props: { basicInfo: SurveyBasicRequest; setBasicInfo: (basicInfo: SurveyBasicRequest) => void; mode: Mode }) {
|
||||
const { basicInfo, setBasicInfo, mode } = props
|
||||
const { setBasicInfoSelected } = useSurveySaleTabState()
|
||||
const [isFlip, setIsFlip] = useState<boolean>(true)
|
||||
|
||||
const { session } = useSessionStore()
|
||||
const { addressData } = useAddressStore()
|
||||
const popupController = usePopupController()
|
||||
|
||||
useEffect(() => {
|
||||
setBasicInfoSelected()
|
||||
}, [])
|
||||
|
||||
// 주소 데이터가 변경될 때만 업데이트
|
||||
// 시공권한 user(Builder), Partner 계정은 조사매물 등록 할 때 STORE_ID에 시공점ID가 들어감
|
||||
// 권한 별 목록 필터링 시 시공권한 user(Builder), Partner는 시공점ID가 같은 것들만 조회
|
||||
useEffect(() => {
|
||||
if (!addressData) return
|
||||
if (session?.isLoggedIn) {
|
||||
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])
|
||||
|
||||
setBasicInfo({
|
||||
...basicInfo,
|
||||
postCode: addressData.post_code,
|
||||
address: addressData.address,
|
||||
addressDetail: addressData.address_detail,
|
||||
})
|
||||
}, [addressData])
|
||||
const popupController = usePopupController()
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -59,15 +67,29 @@ export default function BasicForm({ basicInfo, setBasicInfo, mode, session }: Ba
|
||||
onChange={(e) => setBasicInfo({ ...basicInfo, representative: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
{mode === 'READ' || session?.role === 'Builder' ? (
|
||||
<>
|
||||
{storeInput(basicInfo, setBasicInfo, mode)}
|
||||
{builderInput(basicInfo, setBasicInfo, mode)}
|
||||
</>
|
||||
) : session?.role === 'Partner' ? (
|
||||
<>{builderInput(basicInfo, setBasicInfo, mode)}</>
|
||||
) : (
|
||||
<>{storeInput(basicInfo, setBasicInfo, mode)}</>
|
||||
{(session?.role === 'Builder' || session?.role?.includes('Admin')) && (
|
||||
<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>
|
||||
)}
|
||||
{(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>
|
||||
@ -147,33 +169,3 @@ export default function BasicForm({ basicInfo, setBasicInfo, mode, session }: Ba
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -7,88 +7,76 @@ import { useParams, useRouter, useSearchParams } from 'next/navigation'
|
||||
import { requiredFields, useSurvey } from '@/hooks/useSurvey'
|
||||
import { usePopupController } from '@/store/popupController'
|
||||
|
||||
interface ButtonFormProps {
|
||||
export default function ButtonForm(props: {
|
||||
mode: Mode
|
||||
setMode: (mode: Mode) => void
|
||||
data: {
|
||||
basic: SurveyBasicRequest
|
||||
roof: SurveyDetailRequest
|
||||
}
|
||||
}
|
||||
|
||||
interface PermissionState {
|
||||
isSubmiter: boolean
|
||||
isWriter: boolean
|
||||
isReceiver: boolean
|
||||
}
|
||||
|
||||
interface SaveData extends SurveyBasicRequest {
|
||||
detailInfo: SurveyDetailRequest
|
||||
}
|
||||
|
||||
export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
|
||||
data: { basic: SurveyBasicRequest; roof: SurveyDetailRequest }
|
||||
}) {
|
||||
// 라우터
|
||||
const router = useRouter()
|
||||
const { mode, setMode } = props
|
||||
const { session } = useSessionStore()
|
||||
|
||||
const searchParams = useSearchParams()
|
||||
const idParam = searchParams.get('id')
|
||||
|
||||
const params = useParams()
|
||||
const routeId = params.id
|
||||
|
||||
const popupController = usePopupController()
|
||||
|
||||
const [saveData, setSaveData] = useState<SaveData>({
|
||||
...data.basic,
|
||||
detailInfo: data.roof,
|
||||
// ------------------------------------------------------------
|
||||
const [saveData, setSaveData] = useState({
|
||||
...props.data.basic,
|
||||
detailInfo: props.data.roof,
|
||||
})
|
||||
// --------------------------------------------------------------
|
||||
// 권한
|
||||
|
||||
const [permissions, setPermissions] = useState<PermissionState>({
|
||||
isSubmiter: false,
|
||||
isWriter: false,
|
||||
isReceiver: false,
|
||||
})
|
||||
|
||||
const isSubmit = data.basic.submissionStatus
|
||||
const id = Number(routeId) ? Number(routeId) : Number(idParam)
|
||||
|
||||
const { deleteSurvey, updateSurvey, isDeletingSurvey, isUpdatingSurvey } = useSurvey(id)
|
||||
const { validateSurveyDetail, createSurvey, isCreatingSurvey } = useSurvey()
|
||||
// 제출권한 ㅇ
|
||||
const [isSubmiter, setIsSubmiter] = useState(false)
|
||||
// 작성자
|
||||
const [isWriter, setIsWriter] = useState(false)
|
||||
const isSubmit = props.data.basic.submissionStatus
|
||||
|
||||
useEffect(() => {
|
||||
if (!session?.isLoggedIn) return
|
||||
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
|
||||
}
|
||||
|
||||
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.builderId === basicData.constructionPointId
|
||||
case 'Builder':
|
||||
case 'Partner':
|
||||
return session.builderId === basicData.constructionPointId
|
||||
default:
|
||||
return false
|
||||
setIsWriter(session.userNm === props.data.basic.representative)
|
||||
}
|
||||
}
|
||||
setSaveData({
|
||||
...props.data.basic,
|
||||
detailInfo: props.data.roof,
|
||||
})
|
||||
}, [session, props.data])
|
||||
|
||||
const handleSave = (isTemporary: boolean, isSubmitProcess: boolean) => {
|
||||
const emptyField = validateSurveyDetail(data.roof)
|
||||
// ------------------------------------------------------------
|
||||
// 저장/임시저장/수정
|
||||
const id = Number(routeId) ? Number(routeId) : Number(idParam)
|
||||
|
||||
const { deleteSurvey, updateSurvey, isDeletingSurvey, isUpdatingSurvey } = useSurvey(Number(id))
|
||||
const { validateSurveyDetail, createSurvey, isCreatingSurvey } = useSurvey()
|
||||
|
||||
const handleSave = (isTemporary: boolean, isSubmitProcess = false) => {
|
||||
const emptyField = validateSurveyDetail(props.data.roof)
|
||||
const hasEmptyField = emptyField?.trim() !== ''
|
||||
|
||||
if (isTemporary) {
|
||||
@ -101,65 +89,53 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
|
||||
const tempSaveProcess = async () => {
|
||||
if (idParam) {
|
||||
await updateSurvey({ survey: saveData, isTemporary: true })
|
||||
if (!isUpdatingSurvey) {
|
||||
router.push(`/survey-sale/${idParam}`)
|
||||
}
|
||||
router.push(`/survey-sale/${idParam}`)
|
||||
} else {
|
||||
const updatedData = {
|
||||
...saveData,
|
||||
srlNo: '一時保存',
|
||||
}
|
||||
const id = await createSurvey(updatedData)
|
||||
if (!isCreatingSurvey) {
|
||||
router.push(`/survey-sale/${id}`)
|
||||
}
|
||||
router.push(`/survey-sale/${id}`)
|
||||
}
|
||||
alert('一時保存されました。')
|
||||
}
|
||||
|
||||
const focusInput = (field: keyof SurveyDetailInfo) => {
|
||||
const input = document.getElementById(field)
|
||||
input?.focus()
|
||||
if (input) {
|
||||
input.focus()
|
||||
}
|
||||
}
|
||||
|
||||
const saveProcess = async (emptyField: string | null, isSubmitProcess?: boolean) => {
|
||||
if (emptyField?.trim() === '') {
|
||||
await handleSuccessfulSave(isSubmitProcess)
|
||||
} else {
|
||||
handleFailedSave(emptyField)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSuccessfulSave = async (isSubmitProcess?: boolean) => {
|
||||
if (idParam) {
|
||||
await updateSurvey({ survey: saveData, isTemporary: false, storeId: session.storeId ?? '' })
|
||||
if (!isUpdatingSurvey) {
|
||||
if (idParam) {
|
||||
await updateSurvey({ survey: saveData, isTemporary: false, storeId: session.storeId ?? '' })
|
||||
router.push(`/survey-sale/${idParam}`)
|
||||
}
|
||||
} else {
|
||||
const id = await createSurvey(saveData)
|
||||
if (!isCreatingSurvey) {
|
||||
} else {
|
||||
const id = await createSurvey(saveData)
|
||||
router.push(`/survey-sale/${id}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (isSubmitProcess) {
|
||||
if (!isCreatingSurvey && !isUpdatingSurvey) {
|
||||
await popupController.setSurveySaleSubmitPopup(true)
|
||||
if (isSubmitProcess) {
|
||||
if (!isCreatingSurvey && !isUpdatingSurvey) {
|
||||
popupController.setSurveySaleSubmitPopup(true)
|
||||
}
|
||||
} else {
|
||||
alert('保存されました。')
|
||||
}
|
||||
} else {
|
||||
alert('保存されました。')
|
||||
if (emptyField?.includes('Unit')) {
|
||||
alert('電気契約容量の単位を入力してください。')
|
||||
focusInput(emptyField as keyof SurveyDetailInfo)
|
||||
} else {
|
||||
alert(requiredFields.find((field) => field.field === emptyField)?.name + ' 項目が空です。')
|
||||
focusInput(emptyField as keyof SurveyDetailInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const 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 () => {
|
||||
if (routeId) {
|
||||
@ -174,11 +150,10 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (data.basic.srlNo?.startsWith('一時保存') && Number(routeId)) {
|
||||
if (props.data.basic.srlNo?.startsWith('一時保存') && Number(routeId)) {
|
||||
alert('一時保存されたデータは提出できません。')
|
||||
return
|
||||
}
|
||||
|
||||
if (Number(routeId)) {
|
||||
window.neoConfirm('提出しますか?', async () => {
|
||||
popupController.setSurveySaleSubmitPopup(true)
|
||||
@ -190,15 +165,17 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!session?.isLoggedIn) return null
|
||||
// ------------------------------------------------------------
|
||||
|
||||
if (mode === 'READ' && isSubmit && permissions.isSubmiter) {
|
||||
if (mode === 'READ' && isSubmit && isSubmiter) {
|
||||
return (
|
||||
<div className="sale-form-btn-wrap">
|
||||
<div className="btn-flex-wrap">
|
||||
<ListButton />
|
||||
<>
|
||||
<div className="sale-form-btn-wrap">
|
||||
<div className="btn-flex-wrap">
|
||||
<ListButton />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -208,11 +185,9 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
|
||||
<div className="sale-form-btn-wrap">
|
||||
<div className="btn-flex-wrap">
|
||||
<ListButton />
|
||||
{(permissions.isWriter || permissions.isSubmiter || (permissions.isReceiver && isSubmit)) && (
|
||||
<EditButton setMode={setMode} id={id.toString()} />
|
||||
)}
|
||||
{(permissions.isWriter || (permissions.isReceiver && isSubmit)) && <DeleteButton handleDelete={handleDelete} />}
|
||||
{!isSubmit && permissions.isSubmiter && <SubmitButton handleSubmit={handleSubmit} />}
|
||||
<EditButton setMode={setMode} id={id.toString()} mode={mode} />
|
||||
{(isWriter || !isSubmiter) && <DeleteButton handleDelete={handleDelete} />}
|
||||
{!isSubmit && isSubmiter && <SubmitButton handleSubmit={handleSubmit} />}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -221,9 +196,9 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
|
||||
<div className="sale-form-btn-wrap">
|
||||
<div className="btn-flex-wrap">
|
||||
<ListButton />
|
||||
<TempButton handleSave={() => handleSave(true, false)} />
|
||||
<SaveButton handleSave={() => handleSave(false, false)} />
|
||||
{session?.role === 'T01' || isSubmit ? null : <SubmitButton handleSubmit={handleSubmit} />}
|
||||
<TempButton setMode={setMode} handleSave={handleSave} />
|
||||
<SaveButton handleSave={handleSave} />
|
||||
{session?.role !== 'T01' && <SubmitButton handleSubmit={handleSubmit} />}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -231,11 +206,12 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
|
||||
)
|
||||
}
|
||||
|
||||
// Button Components
|
||||
const ListButton = () => {
|
||||
// 목록 버튼
|
||||
function ListButton() {
|
||||
const router = useRouter()
|
||||
return (
|
||||
<div className="btn-bx">
|
||||
{/* 목록 */}
|
||||
<button className="btn-frame n-blue icon" onClick={() => router.push('/survey-sale')}>
|
||||
リスト<i className="btn-arr"></i>
|
||||
</button>
|
||||
@ -243,10 +219,12 @@ const ListButton = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const EditButton = ({ setMode, id }: { setMode: (mode: Mode) => void; id: string }) => {
|
||||
function EditButton(props: { setMode: (mode: Mode) => void; id: string; mode: Mode }) {
|
||||
const { setMode, id, mode } = props
|
||||
const router = useRouter()
|
||||
return (
|
||||
<div className="btn-bx">
|
||||
{/* 수정 */}
|
||||
<button
|
||||
className="btn-frame n-blue icon"
|
||||
onClick={() => {
|
||||
@ -260,34 +238,59 @@ const EditButton = ({ setMode, id }: { setMode: (mode: Mode) => void; id: string
|
||||
)
|
||||
}
|
||||
|
||||
const SubmitButton = ({ handleSubmit }: { handleSubmit: () => void }) => (
|
||||
<div className="btn-bx">
|
||||
<button className="btn-frame red icon" onClick={handleSubmit}>
|
||||
提出<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
function SubmitButton(props: { handleSubmit: () => void }) {
|
||||
const { handleSubmit } = props
|
||||
return (
|
||||
<>
|
||||
<div className="btn-bx">
|
||||
{/* 제출 */}
|
||||
<button className="btn-frame red icon" onClick={handleSubmit}>
|
||||
提出<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const DeleteButton = ({ handleDelete }: { handleDelete: () => void }) => (
|
||||
<div className="btn-bx">
|
||||
<button className="btn-frame n-blue icon" onClick={handleDelete}>
|
||||
削除<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
function DeleteButton(props: { handleDelete: () => void }) {
|
||||
const { handleDelete } = props
|
||||
return (
|
||||
<div className="btn-bx">
|
||||
{/* 삭제 */}
|
||||
<button className="btn-frame n-blue icon" onClick={handleDelete}>
|
||||
削除<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const SaveButton = ({ handleSave }: { handleSave: () => void }) => (
|
||||
<div className="btn-bx">
|
||||
<button className="btn-frame n-blue icon" onClick={handleSave}>
|
||||
保存<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
function SaveButton(props: { handleSave: (isTemporary: boolean) => void }) {
|
||||
const { handleSave } = props
|
||||
return (
|
||||
<div className="btn-bx">
|
||||
{/* 저장 */}
|
||||
<button className="btn-frame n-blue icon" onClick={() => handleSave(false)}>
|
||||
保存<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const TempButton = ({ handleSave }: { handleSave: () => void }) => (
|
||||
<div className="btn-bx">
|
||||
<button className="btn-frame n-blue icon" onClick={handleSave}>
|
||||
一時保存<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
function TempButton(props: { setMode: (mode: Mode) => void; handleSave: (isTemporary: boolean) => void }) {
|
||||
const { setMode, handleSave } = props
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div className="btn-bx">
|
||||
{/* 임시저장 */}
|
||||
<button
|
||||
className="btn-frame n-blue icon"
|
||||
onClick={() => {
|
||||
handleSave(true)
|
||||
}}
|
||||
>
|
||||
一時保存<i className="btn-arr"></i>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -4,15 +4,12 @@ import { useSurvey } from '@/hooks/useSurvey'
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
import { useEffect } from 'react'
|
||||
import DetailForm from './DetailForm'
|
||||
import { useSessionStore } from '@/store/session'
|
||||
|
||||
export default function DataTable() {
|
||||
const params = useParams()
|
||||
const id = params.id
|
||||
const router = useRouter()
|
||||
|
||||
const { session } = useSessionStore()
|
||||
|
||||
useEffect(() => {
|
||||
if (Number.isNaN(Number(id))) {
|
||||
alert('間違ったアプローチです。')
|
||||
@ -23,25 +20,7 @@ export default function DataTable() {
|
||||
const { surveyDetail, isLoadingSurveyDetail } = useSurvey(Number(id))
|
||||
|
||||
if (isLoadingSurveyDetail) {
|
||||
return null
|
||||
}
|
||||
|
||||
const submitStatus = () => {
|
||||
const { submissionTargetNm, submissionTargetId } = surveyDetail ?? {}
|
||||
|
||||
if (!submissionTargetNm) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!submissionTargetId) {
|
||||
return <div>{submissionTargetNm}</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
({submissionTargetNm} - {submissionTargetId})
|
||||
</div>
|
||||
)
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
return (
|
||||
@ -77,7 +56,9 @@ export default function DataTable() {
|
||||
{surveyDetail?.submissionStatus && surveyDetail?.submissionDate ? (
|
||||
<>
|
||||
<div>{new Date(surveyDetail.submissionDate).toLocaleString()}</div>
|
||||
{submitStatus()}
|
||||
<div>
|
||||
({surveyDetail.store} - {surveyDetail.storeId})
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
'-'
|
||||
|
||||
@ -7,7 +7,6 @@ import BasicForm from './BasicForm'
|
||||
import RoofForm from './RoofForm'
|
||||
import { useParams, useSearchParams } from 'next/navigation'
|
||||
import { useSurvey } from '@/hooks/useSurvey'
|
||||
import { useSessionStore } from '@/store/session'
|
||||
|
||||
const roofInfoForm: SurveyDetailRequest = {
|
||||
contractCapacity: null,
|
||||
@ -53,7 +52,6 @@ const basicInfoForm: SurveyBasicRequest = {
|
||||
store: null,
|
||||
storeId: null,
|
||||
constructionPoint: null,
|
||||
constructionPointId: null,
|
||||
investigationDate: new Date().toLocaleDateString('en-CA'),
|
||||
buildingName: null,
|
||||
customerName: null,
|
||||
@ -63,7 +61,6 @@ const basicInfoForm: SurveyBasicRequest = {
|
||||
submissionStatus: false,
|
||||
submissionDate: null,
|
||||
submissionTargetId: null,
|
||||
submissionTargetNm: null,
|
||||
srlNo: null,
|
||||
}
|
||||
|
||||
@ -74,54 +71,29 @@ export default function DetailForm() {
|
||||
const modeset = Number(routeId) ? 'READ' : idParam ? 'EDIT' : 'CREATE'
|
||||
const id = Number(routeId) ? Number(routeId) : Number(idParam)
|
||||
|
||||
const { surveyDetail, isLoadingSurveyDetail, validateSurveyDetail } = useSurvey(Number(id))
|
||||
const { session } = useSessionStore()
|
||||
const { surveyDetail, validateSurveyDetail } = useSurvey(Number(id))
|
||||
|
||||
const [mode, setMode] = useState<Mode>(modeset)
|
||||
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?.builderId ?? null,
|
||||
}))
|
||||
const [basicInfoData, setBasicInfoData] = useState<SurveyBasicRequest>(basicInfoForm)
|
||||
const [roofInfoData, setRoofInfoData] = useState<SurveyDetailRequest>(roofInfoForm)
|
||||
|
||||
// 세션 데이터가 변경될 때 기본 정보 업데이트
|
||||
useEffect(() => {
|
||||
if (!session?.isLoggedIn) return
|
||||
setBasicInfoData((prev) => ({
|
||||
...prev,
|
||||
representative: session.userNm ?? '',
|
||||
representativeId: session.userId ?? null,
|
||||
store: session.storeNm ?? null,
|
||||
storeId: session.storeId ?? null,
|
||||
constructionPoint: session.builderNm ?? null,
|
||||
constructionPointId: session.builderId ?? null,
|
||||
}))
|
||||
}, [session?.isLoggedIn])
|
||||
if (Number(idParam) !== 0 && surveyDetail === null) {
|
||||
alert('データが見つかりません。')
|
||||
window.location.href = '/survey-sale'
|
||||
}
|
||||
|
||||
// 설문 데이터 로딩 및 업데이트
|
||||
useEffect(() => {
|
||||
if (isLoadingSurveyDetail || !session?.isLoggedIn) return
|
||||
if (surveyDetail && (mode === 'EDIT' || mode === 'READ')) {
|
||||
const { id, uptDt, regDt, detailInfo, ...rest } = surveyDetail
|
||||
setBasicInfoData((prev) => ({
|
||||
...prev,
|
||||
...rest,
|
||||
}))
|
||||
|
||||
setBasicInfoData(rest)
|
||||
if (detailInfo) {
|
||||
const { id, uptDt, regDt, basicInfoId, ...rest } = detailInfo
|
||||
setRoofInfoData(rest)
|
||||
if (validateSurveyDetail(rest).trim() !== '') {
|
||||
// validation logic here if needed
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [mode, session?.isLoggedIn, isLoadingSurveyDetail])
|
||||
}, [surveyDetail, id])
|
||||
|
||||
const data = {
|
||||
basic: basicInfoData,
|
||||
@ -133,7 +105,9 @@ export default function DetailForm() {
|
||||
return (
|
||||
<>
|
||||
<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} />
|
||||
<ButtonForm {...buttonFormProps} />
|
||||
</div>
|
||||
|
||||
@ -137,7 +137,7 @@ export const radioEtcData: Record<RadioEtcKeys, { id: number; label: string }[]>
|
||||
structureOrder: [
|
||||
{
|
||||
id: 1,
|
||||
label: '屋根材 > 防水材 > 屋根の基礎 > 垂木', //지붕재 방수재 지붕의기초 서까래
|
||||
label: '屋根材 - 防水材 - 屋根の基礎 - 垂木', //지붕재 방수재 지붕의기초 서까래
|
||||
},
|
||||
],
|
||||
houseStructure: [
|
||||
@ -537,7 +537,6 @@ const SelectedBox = ({
|
||||
</select>
|
||||
<div className={`data-input ${column === 'constructionYear' ? 'flex' : ''}`}>
|
||||
<input
|
||||
id={`${column}Etc`}
|
||||
type={column === 'constructionYear' ? 'number' : 'text'}
|
||||
inputMode={column === 'constructionYear' ? 'numeric' : 'text'}
|
||||
className="input-frame"
|
||||
@ -643,7 +642,6 @@ const RadioSelected = ({
|
||||
{(showEtcOption || column === 'insulationPresence') && (
|
||||
<div className="data-input">
|
||||
<input
|
||||
id={`${column}Etc`}
|
||||
type="text"
|
||||
className="input-frame"
|
||||
placeholder="-"
|
||||
|
||||
@ -28,11 +28,6 @@ export default function ListTable() {
|
||||
|
||||
useEffect(() => {
|
||||
if (!session.isLoggedIn || isLoadingSurveyList) return
|
||||
// if ('status' in surveyList && surveyList.status === 403) {
|
||||
// alert('権限がありません。')
|
||||
// router.push('/survey-sale')
|
||||
// return
|
||||
// }
|
||||
if ('count' in surveyList && surveyList.count > 0) {
|
||||
if (offset > 0) {
|
||||
setHeldSurveyList((prev) => [...prev, ...surveyList.data])
|
||||
@ -59,7 +54,7 @@ export default function ListTable() {
|
||||
{heldSurveyList.map((survey) => (
|
||||
<li className="sale-list-item cursor-pointer" key={survey.id} onClick={() => handleDetailClick(survey.id)}>
|
||||
<div className="sale-item-bx">
|
||||
<div className="sale-item-date-bx">
|
||||
<div className="sale-item-date-bx">
|
||||
<div className="sale-item-num">{survey.srlNo}</div>
|
||||
<div className="sale-item-date">{survey.investigationDate}</div>
|
||||
</div>
|
||||
|
||||
@ -15,7 +15,7 @@ export default function SearchForm({ memberRole, userId }: { memberRole: string;
|
||||
alert('2文字以上入力してください')
|
||||
return
|
||||
}
|
||||
setOffset(0)
|
||||
reset()
|
||||
setKeyword(searchKeyword)
|
||||
setSearchOption(option)
|
||||
}
|
||||
@ -72,16 +72,6 @@ export default function SearchForm({ memberRole, userId }: { memberRole: string;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{searchKeyword && (
|
||||
<button
|
||||
className="del-icon"
|
||||
onClick={() => {
|
||||
setSearchKeyword('')
|
||||
setKeyword('')
|
||||
setOffset(0)
|
||||
}}
|
||||
></button>
|
||||
)}
|
||||
<button className="search-icon" onClick={handleSearch}></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,10 +1,21 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import Config from '@/config/config.export'
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<>
|
||||
<footer>
|
||||
<div className="footer-inner">COPYRIGHT©2025 Hanwha Japan All Rights Reserved </div>
|
||||
<div className="footer-inner">
|
||||
COPYRIGHT©2025 Hanwha Japan All Rights Reserved{' '}
|
||||
<span>
|
||||
<Link href="/pdf/suitable">PDF</Link>
|
||||
</span>
|
||||
<span>{Config().mode}</span>
|
||||
<span>{Config().baseUrl}</span>
|
||||
<span>{process.env.NEXT_PUBLIC_API_URL}</span>
|
||||
</div>
|
||||
</footer>
|
||||
</>
|
||||
)
|
||||
|
||||
@ -11,8 +11,6 @@ import { useSideNavState } from '@/store/sideNavState'
|
||||
import { useHeaderStore } from '@/store/header'
|
||||
import { useSessionStore } from '@/store/session'
|
||||
import { usePopupController } from '@/store/popupController'
|
||||
import { useSurveyFilterStore } from '@/store/surveyFilterStore'
|
||||
import { useInquiryFilterStore } from '@/store/inquiryFilterStore'
|
||||
|
||||
import { useTitle } from '@/hooks/useTitle'
|
||||
import { useAxios } from '@/hooks/useAxios'
|
||||
@ -32,9 +30,6 @@ export default function Header() {
|
||||
|
||||
const popupController = usePopupController()
|
||||
|
||||
const { setIsMySurvey } = useSurveyFilterStore()
|
||||
const { setInquiryListRequest, inquiryListRequest } = useInquiryFilterStore()
|
||||
|
||||
if (pathname === '/login') {
|
||||
return null
|
||||
}
|
||||
@ -85,27 +80,13 @@ export default function Header() {
|
||||
<div className="side-swiper-wrap">
|
||||
<Swiper slidesPerView={1.6} spaceBetween={12} className="mySwiper">
|
||||
<SwiperSlide>
|
||||
<div
|
||||
className="side-swiper-card"
|
||||
onClick={() => {
|
||||
setIsMySurvey(session?.userId)
|
||||
router.push('/survey-sale')
|
||||
setSideNavIsOpen(false)
|
||||
}}
|
||||
>
|
||||
<div className="side-swiper-card">
|
||||
<div className="side-swiper-icon icon01"></div>
|
||||
<div className="side-swiper-infor">私は作成した物件</div>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
<SwiperSlide>
|
||||
<div
|
||||
className="side-swiper-card"
|
||||
onClick={() => {
|
||||
setInquiryListRequest({ ...inquiryListRequest, schRegId: session?.userId })
|
||||
router.push('/inquiry/list')
|
||||
setSideNavIsOpen(false)
|
||||
}}
|
||||
>
|
||||
<div className="side-swiper-card">
|
||||
<div className="side-swiper-icon icon02"></div>
|
||||
<div className="side-swiper-infor">私は作成したお問 い合わせ</div>
|
||||
</div>
|
||||
@ -124,7 +105,7 @@ export default function Header() {
|
||||
<button onClick={() => router.push('/survey-sale')}>調査物件一覧</button>
|
||||
</li>
|
||||
<li className="side-nav-item">
|
||||
<button onClick={() => router.push('/survey-sale/regist')}>調査物件登録</button>
|
||||
<button onClick={() => router.push('/survey-sale/basic-info')}>調査物件登録</button>
|
||||
</li>
|
||||
<li className="side-nav-item">
|
||||
<button onClick={() => router.push('/inquiry/list')}>1:1お問い合わせ</button>
|
||||
|
||||
@ -3,21 +3,21 @@ import Config from '@/config/config.export'
|
||||
import { useSpinnerStore } from '@/store/spinnerStore'
|
||||
|
||||
export function useAxios() {
|
||||
// const { setIsShow } = useSpinnerStore()
|
||||
|
||||
const requestHandler = (config: InternalAxiosRequestConfig) => {
|
||||
useSpinnerStore.getState().setIsShow(true)
|
||||
// setIsShow(true)
|
||||
return config
|
||||
}
|
||||
|
||||
const responseHandler = (response: AxiosResponse) => {
|
||||
// if (response.headers['spinner-state'] === undefined) {
|
||||
useSpinnerStore.getState().setIsShow(false)
|
||||
// }
|
||||
// setIsShow(false)
|
||||
response.data = transferResponse(response)
|
||||
return response
|
||||
}
|
||||
|
||||
const errorHandler = (error: any) => {
|
||||
useSpinnerStore.getState().setIsShow(false)
|
||||
// setIsShow(false)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
|
||||
@ -76,22 +76,19 @@ export function useInquiry(
|
||||
|
||||
const downloadFile = async (encodeFileNo: number, srcFileNm: string) => {
|
||||
try {
|
||||
const resp = await fetch(`/api/qna/file?encodeFileNo=${encodeFileNo}&srcFileNm=${srcFileNm}`)
|
||||
|
||||
const blob = await resp.blob()
|
||||
const resp = await axiosInstance(null).get<Blob>(`/api/qna/file`, { params: { encodeFileNo, srcFileNm } })
|
||||
const blob = new Blob([resp.data], { type: 'application/octet-stream;charset=UTF-8' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = srcFileNm
|
||||
document.body.appendChild(a)
|
||||
a.download = `${srcFileNm}`
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
|
||||
return blob
|
||||
} catch (error) {
|
||||
console.error('File download error:', error)
|
||||
alert('ファイルのダウンロードに失敗しました')
|
||||
} catch (error: any) {
|
||||
if (error.response.status === 404) {
|
||||
alert('ファイルが見つかりません')
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { useInfiniteQuery, useQuery } from '@tanstack/react-query'
|
||||
import { SHA256 } from 'crypto-js'
|
||||
import { useInfiniteQuery } from '@tanstack/react-query'
|
||||
import { transformObjectKeys } from '@/libs/axios'
|
||||
import { useSuitableStore } from '@/store/useSuitableStore'
|
||||
import { useAxios } from './useAxios'
|
||||
@ -19,7 +18,6 @@ export function useSuitable() {
|
||||
clearSearchKeyword,
|
||||
selectedItems,
|
||||
clearSelectedItems,
|
||||
selectedItemsSearching,
|
||||
} = useSuitableStore()
|
||||
|
||||
const getSuitables = async ({
|
||||
@ -87,7 +85,6 @@ export function useSuitable() {
|
||||
}
|
||||
|
||||
const toSuitableDetail = (suitableDetailString: string): SuitableDetail[] => {
|
||||
if (!suitableDetailString) return []
|
||||
try {
|
||||
const suitableDetailArray = transformObjectKeys(JSON.parse(suitableDetailString)) as SuitableDetail[]
|
||||
if (!Array.isArray(suitableDetailArray)) {
|
||||
@ -115,13 +112,13 @@ export function useSuitable() {
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
isLoading,
|
||||
// isError,
|
||||
// error,
|
||||
isError,
|
||||
error,
|
||||
} = useInfiniteQuery<Suitable[]>({
|
||||
queryKey: ['suitables', 'list', selectedCategory, searchKeyword],
|
||||
queryFn: async (context) => {
|
||||
const pageParam = context.pageParam as number
|
||||
if (pageParam === 1) clearSuitableStore({ items: true })
|
||||
if (pageParam === 1) clearSuitableSearch({ items: true })
|
||||
return await getSuitables({
|
||||
pageNumber: pageParam,
|
||||
...(selectedCategory && { category: selectedCategory }),
|
||||
@ -147,96 +144,35 @@ export function useSuitable() {
|
||||
return { ids: ids.join(','), detailIds: detailIds.length > 0 ? detailIds.join(',') : '' }
|
||||
}
|
||||
|
||||
const getSelectedItemsHash = (): string => {
|
||||
const entries = Array.from(selectedItems.entries())
|
||||
.map(([key, value]) => `${key}:${Array.from(value).sort().join(',')}`)
|
||||
.sort()
|
||||
.join('|')
|
||||
return SHA256(entries).toString()
|
||||
const getSelectedItemsData = async (): Promise<Suitable[]> => {
|
||||
const { ids, detailIds } = serializeSelectedItems()
|
||||
return await getSuitableDetails(ids, detailIds)
|
||||
}
|
||||
|
||||
const {
|
||||
data: selectedSuitables,
|
||||
isLoading: isSelectedSuitablesLoading,
|
||||
// refetch: refetchSelectedSuitables,
|
||||
} = useQuery<Suitable[]>({
|
||||
queryKey: ['suitables', 'selectedItems', getSelectedItemsHash(), selectedItemsSearching],
|
||||
queryFn: async () => {
|
||||
const { ids, detailIds } = serializeSelectedItems()
|
||||
return await getSuitableDetails(ids, detailIds)
|
||||
},
|
||||
staleTime: Infinity,
|
||||
gcTime: Infinity,
|
||||
enabled: selectedItemsSearching,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
})
|
||||
|
||||
const clearSuitableStore = ({ items = false, category = false, keyword = false }: { items?: boolean; category?: boolean; keyword?: boolean }) => {
|
||||
const clearSuitableSearch = ({ items = false, category = false, keyword = false }: { items?: boolean; category?: boolean; keyword?: boolean }) => {
|
||||
if (items) clearSelectedItems()
|
||||
if (category) clearSelectedCategory()
|
||||
if (keyword) clearSearchKeyword()
|
||||
}
|
||||
|
||||
// TODO: 추후 지붕재 적합성 데이터 CUD 구현 시 ×, -, ー 데이터 관리 필요
|
||||
// TODO: 추후 지붕재 적합성 데이터 CUD 구현 시 ×, ー 데이터 관리 필요
|
||||
const suitableCheckIcon = (value: string): string => {
|
||||
const iconMap: Record<string, string> = {
|
||||
'×': '/assets/images/sub/compliance_x_icon.svg',
|
||||
'-': '/assets/images/sub/compliance_quest_icon.svg',
|
||||
'ー': '/assets/images/sub/compliance_quest_icon.svg',
|
||||
default: '/assets/images/sub/compliance_check_icon.svg',
|
||||
}
|
||||
return iconMap[value] || iconMap.default
|
||||
}
|
||||
|
||||
// TODO: 추후 지붕재 적합성 데이터 CUD 구현 시 ○, ×, -, ー 데이터 관리 필요
|
||||
// TODO: 추후 지붕재 적합성 데이터 CUD 구현 시 ○, ×, ー 데이터 관리 필요
|
||||
const suitableCheckMemo = (value: string): string => {
|
||||
if (value === '○') return '設置可'
|
||||
if (value === '×') return '設置不可'
|
||||
if (value === '-' || value === 'ー') return 'お問い合わせください'
|
||||
if (value === 'ー') return 'お問い合わせください'
|
||||
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 {
|
||||
getSuitables,
|
||||
getSuitableIds,
|
||||
@ -250,11 +186,9 @@ export function useSuitable() {
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
isLoading,
|
||||
selectedSuitables,
|
||||
isSelectedSuitablesLoading,
|
||||
clearSuitableStore,
|
||||
getSelectedItemsData,
|
||||
clearSuitableSearch,
|
||||
suitableCheckIcon,
|
||||
suitableCheckMemo,
|
||||
downloadSuitablePdf,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import type { SurveyBasicInfo, SurveyDetailRequest, SurveyRegistRequest } from '@/types/Survey'
|
||||
import { useMemo, useEffect } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useSurveyFilterStore } from '@/store/surveyFilterStore'
|
||||
import { useSessionStore } from '@/store/session'
|
||||
import { useAxios } from './useAxios'
|
||||
import { queryStringFormatter } from '@/utils/common-utils'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
export const requiredFields = [
|
||||
{
|
||||
@ -67,7 +66,7 @@ export function useSurvey(id?: number): {
|
||||
createSurvey: (survey: SurveyRegistRequest) => Promise<number>
|
||||
updateSurvey: ({ survey, isTemporary, storeId }: { survey: SurveyRegistRequest; isTemporary: boolean; storeId?: string }) => void
|
||||
deleteSurvey: () => Promise<boolean>
|
||||
submitSurvey: (params: { targetId?: string | null; targetNm?: string | null }) => void
|
||||
submitSurvey: (params: { saveId?: number; targetId?: string; storeId?: string; srlNo?: string }) => void
|
||||
validateSurveyDetail: (surveyDetail: SurveyDetailRequest) => string
|
||||
getZipCode: (zipCode: string) => Promise<ZipCode[] | null>
|
||||
refetchSurveyList: () => void
|
||||
@ -76,46 +75,14 @@ export function useSurvey(id?: number): {
|
||||
const { keyword, searchOption, isMySurvey, sort, offset } = useSurveyFilterStore()
|
||||
const { session } = useSessionStore()
|
||||
const { axiosInstance } = useAxios()
|
||||
const router = useRouter()
|
||||
|
||||
const checkSession = () => {
|
||||
if (session?.isLoggedIn) {
|
||||
switch (session?.role) {
|
||||
case 'T01':
|
||||
case 'Admin':
|
||||
case 'Admin_Sub':
|
||||
if (session?.storeId === null) {
|
||||
alert('販売店IDがありません。')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
case 'Builder':
|
||||
case 'Partner':
|
||||
if (session?.builderId === null) {
|
||||
alert('施工店IDがありません。')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
default:
|
||||
alert('権限が間違っています。')
|
||||
return false
|
||||
}
|
||||
}
|
||||
alert('ログインしていません。')
|
||||
return false
|
||||
}
|
||||
|
||||
const {
|
||||
data: surveyListData,
|
||||
isLoading: isLoadingSurveyList,
|
||||
refetch: refetchSurveyList,
|
||||
} = useQuery({
|
||||
queryKey: ['survey', 'list', keyword, searchOption, isMySurvey, sort, offset, session?.storeNm, session?.builderId, session?.role],
|
||||
queryKey: ['survey', 'list', keyword, searchOption, isMySurvey, sort, offset, session?.storeNm, session?.builderNo, session?.role],
|
||||
queryFn: async () => {
|
||||
if (!checkSession()) {
|
||||
router.replace('/')
|
||||
return { data: [], count: 0 }
|
||||
}
|
||||
const resp = await axiosInstance(null).get<{ data: SurveyBasicInfo[]; count: number }>('/api/survey-sales', {
|
||||
params: {
|
||||
keyword,
|
||||
@ -123,8 +90,8 @@ export function useSurvey(id?: number): {
|
||||
isMySurvey,
|
||||
sort,
|
||||
offset,
|
||||
storeId: session?.storeId,
|
||||
builderId: session?.builderId,
|
||||
store: session?.storeId,
|
||||
builderNo: session?.builderNo,
|
||||
role: session?.role,
|
||||
},
|
||||
})
|
||||
@ -142,28 +109,12 @@ export function useSurvey(id?: number): {
|
||||
const { data: surveyDetail, isLoading: isLoadingSurveyDetail } = useQuery({
|
||||
queryKey: ['survey', id],
|
||||
queryFn: async () => {
|
||||
if (!checkSession()) {
|
||||
router.replace('/survey-sale')
|
||||
return null
|
||||
}
|
||||
if (id === 0 || id === undefined) return null
|
||||
try {
|
||||
const resp = await axiosInstance(null).get<SurveyBasicInfo>(`/api/survey-sales/${id}`, {
|
||||
params: {
|
||||
role: session?.role,
|
||||
storeId: session?.storeId,
|
||||
builderId: session?.builderId,
|
||||
isLoggedIn: session?.isLoggedIn,
|
||||
},
|
||||
})
|
||||
return resp.data
|
||||
} catch (error: any) {
|
||||
alert(error.response?.data.error)
|
||||
router.replace('/survey-sale')
|
||||
return null
|
||||
}
|
||||
if (id === undefined) throw new Error('id is required')
|
||||
if (id === null || isNaN(id)) return null
|
||||
const resp = await axiosInstance(null).get<SurveyBasicInfo>(`/api/survey-sales/${id}`)
|
||||
return resp.data
|
||||
},
|
||||
enabled: id !== 0 && id !== undefined && session?.isLoggedIn,
|
||||
enabled: id !== undefined,
|
||||
})
|
||||
|
||||
const { mutateAsync: createSurvey, isPending: isCreatingSurvey } = useMutation({
|
||||
@ -212,11 +163,13 @@ export function useSurvey(id?: number): {
|
||||
})
|
||||
|
||||
const { mutateAsync: submitSurvey, isPending: isSubmittingSurvey } = useMutation({
|
||||
mutationFn: async ({ targetId, targetNm }: { targetId?: string | null; targetNm?: string | null }) => {
|
||||
mutationFn: async ({ targetId, storeId, srlNo }: { targetId?: string; storeId?: string; srlNo?: string }) => {
|
||||
if (!id) throw new Error('id is required')
|
||||
const resp = await axiosInstance(null).patch<boolean>(`/api/survey-sales/${id}`, {
|
||||
targetId,
|
||||
targetNm,
|
||||
storeId,
|
||||
srlNo,
|
||||
role: session?.role ?? null,
|
||||
})
|
||||
return resp.data
|
||||
},
|
||||
@ -227,44 +180,34 @@ export function useSurvey(id?: number): {
|
||||
})
|
||||
|
||||
const validateSurveyDetail = (surveyDetail: SurveyDetailRequest) => {
|
||||
// 상수 정의
|
||||
const ETC_FIELDS = ['installationSystem', 'rafterSize', 'rafterPitch', 'waterproofMaterial', 'structureOrder'] as const
|
||||
const etcFields = [
|
||||
'installationSystem',
|
||||
'constructionYear',
|
||||
'rafterSize',
|
||||
'rafterPitch',
|
||||
'waterproofMaterial',
|
||||
'structureOrder',
|
||||
'insulationPresence',
|
||||
] as const
|
||||
|
||||
const SPECIAL_CONDITIONS = ['constructionYear', 'insulationPresence'] as const
|
||||
|
||||
// 유틸리티 함수들
|
||||
const isEmptyValue = (value: any): boolean => {
|
||||
return value === null || value?.toString().trim() === ''
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
const emptyField = requiredFields.find((field) => {
|
||||
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
|
||||
}
|
||||
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) {
|
||||
if (contractCapacity && contractCapacity.trim() !== '' && contractCapacity.split(' ')?.length === 1) {
|
||||
return 'contractCapacityUnit'
|
||||
}
|
||||
return ''
|
||||
|
||||
return emptyField?.field || ''
|
||||
}
|
||||
|
||||
const getZipCode = async (zipCode: string): Promise<ZipCode[] | null> => {
|
||||
|
||||
@ -44,9 +44,7 @@ export const defaultSession: SessionData = {
|
||||
groupId: null,
|
||||
storeLvl: null,
|
||||
custCd: null,
|
||||
builderId: null,
|
||||
builderNo: null,
|
||||
builderNm: null,
|
||||
isLoggedIn: false,
|
||||
role: null,
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ export default function ReactQueryProviders({ children }: React.PropsWithChildre
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 60 * 1000,
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
@ -40,9 +40,7 @@ const initialState: InitialState = {
|
||||
groupId: null,
|
||||
storeLvl: null,
|
||||
custCd: null,
|
||||
builderId: null,
|
||||
builderNo: null,
|
||||
builderNm: null,
|
||||
isLoggedIn: false,
|
||||
role: null,
|
||||
},
|
||||
|
||||
@ -21,18 +21,18 @@ export const SEARCH_OPTIONS = [
|
||||
id: 'store',
|
||||
label: '販売店名',
|
||||
},
|
||||
{
|
||||
id: 'store_id',
|
||||
label: '販売店ID',
|
||||
},
|
||||
// {
|
||||
// id: 'store_id',
|
||||
// label: '販売店ID',
|
||||
// },
|
||||
{
|
||||
id: 'construction_point',
|
||||
label: '施工店名',
|
||||
},
|
||||
{
|
||||
id: 'construction_point_id',
|
||||
label: '施工店ID',
|
||||
},
|
||||
// {
|
||||
// id: 'construction_id',
|
||||
// label: '施工店ID',
|
||||
// },
|
||||
]
|
||||
|
||||
export const SEARCH_OPTIONS_PARTNERS = [
|
||||
|
||||
@ -35,10 +35,6 @@ interface SuitableState {
|
||||
removeSelectedItem: (mainId: number, detailId?: number) => void
|
||||
/* 선택된 아이템 모두 제거 */
|
||||
clearSelectedItems: () => void
|
||||
/* 선택된 아이템 검색 상태 */
|
||||
selectedItemsSearching: boolean
|
||||
/* 선택된 아이템 검색 상태 설정 */
|
||||
setSelectedItemsSearching: (value: boolean) => void
|
||||
}
|
||||
|
||||
export const useSuitableStore = create<SuitableState>((set) => ({
|
||||
@ -47,7 +43,6 @@ export const useSuitableStore = create<SuitableState>((set) => ({
|
||||
selectedCategory: '' as string,
|
||||
searchKeyword: '' as string,
|
||||
selectedItems: new Map() as Map<number, Set<number>>,
|
||||
selectedItemsSearching: false as boolean,
|
||||
|
||||
/* 공통코드 설정 */
|
||||
setSuitableCommCode: (headCode: string, commCode: CommCode[]) =>
|
||||
@ -89,7 +84,7 @@ export const useSuitableStore = create<SuitableState>((set) => ({
|
||||
set(() => {
|
||||
const newSelectedItems = new Map()
|
||||
suitableIds.forEach((suitableId) => {
|
||||
newSelectedItems.set(suitableId.id, new Set(suitableId.detailId?.split(',').map(Number)))
|
||||
newSelectedItems.set(suitableId.id, new Set(suitableId.detailId.split(',').map(Number)))
|
||||
})
|
||||
return { selectedItems: newSelectedItems }
|
||||
})
|
||||
@ -119,7 +114,4 @@ export const useSuitableStore = create<SuitableState>((set) => ({
|
||||
|
||||
/* 선택된 아이템 모두 제거 */
|
||||
clearSelectedItems: () => set({ selectedItems: new Map() as Map<number, Set<number>> }),
|
||||
|
||||
/* 선택된 아이템 검색 상태 설정 */
|
||||
setSelectedItemsSearching: (value: boolean) => set({ selectedItemsSearching: value }),
|
||||
}))
|
||||
|
||||
@ -58,22 +58,11 @@
|
||||
}
|
||||
|
||||
// 지붕재 적합성
|
||||
.pdf-table-wrap{
|
||||
max-width: 1540px;
|
||||
min-width: 1540px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.pdf-intro-page{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 1050px;
|
||||
height: 1080px;
|
||||
padding: 80px 40px ;
|
||||
background-color: #fff;
|
||||
}
|
||||
.pdf-intro-foot-date{
|
||||
margin-top: auto;
|
||||
text-align: right;
|
||||
}
|
||||
.pdf-intro-tit-wrap{
|
||||
text-align: center;
|
||||
.pdf-intro-tit{
|
||||
@ -91,11 +80,7 @@
|
||||
}
|
||||
|
||||
.pdf-table-content{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 1050px;
|
||||
padding: 20px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
.pdf-table-grid-wrap{
|
||||
display: grid;
|
||||
|
||||
@ -1,38 +1,38 @@
|
||||
@use "../abstracts" as *;
|
||||
@use '../abstracts' as *;
|
||||
|
||||
// input form 공통
|
||||
.data-input-form-bx{
|
||||
.data-input-form-bx {
|
||||
margin-bottom: 18px;
|
||||
&:last-child{
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.data-input-form-tit{
|
||||
.data-input-form-tit {
|
||||
@include defaultFont($font-s-13, $font-w-500, $font-c);
|
||||
margin-bottom: 10px;
|
||||
.import{
|
||||
color: #F00;
|
||||
.import {
|
||||
color: #f00;
|
||||
}
|
||||
span{
|
||||
span {
|
||||
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;
|
||||
@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);
|
||||
margin-top: 24px;
|
||||
.btn-bx{
|
||||
.btn-bx {
|
||||
flex: 1;
|
||||
}
|
||||
&.com{
|
||||
.btn-bx{
|
||||
&.com {
|
||||
.btn-bx {
|
||||
flex: 1 1 auto;
|
||||
button{
|
||||
button {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
@ -40,13 +40,13 @@
|
||||
}
|
||||
|
||||
// 매물 common
|
||||
.top-btn{
|
||||
.top-btn {
|
||||
position: fixed;
|
||||
bottom: 96px;
|
||||
right: 15px;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
background-color: rgba(0, 0, 0, 0.50);
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
background-image: url(/assets/images/sub/top_btn_icon.svg);
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
@ -55,68 +55,68 @@
|
||||
z-index: 90000;
|
||||
}
|
||||
|
||||
.sale-contents{
|
||||
.sale-contents {
|
||||
width: 100%;
|
||||
background-color: #F5F5F5;
|
||||
.sale-frame{
|
||||
background-color: #f5f5f5;
|
||||
.sale-frame {
|
||||
padding: 0 20px;
|
||||
border-top: 1px solid #ECECEC;
|
||||
border-bottom: 1px solid #ECECEC;
|
||||
border-top: 1px solid #ececec;
|
||||
border-bottom: 1px solid #ececec;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 24px;
|
||||
padding-top: 24px;
|
||||
background-color: $white-fff;
|
||||
&:first-child{
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
border-top: none;
|
||||
}
|
||||
&:last-child{
|
||||
&:last-child {
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.sale-form-btn-wrap{
|
||||
padding: 20px 20px 0 ;
|
||||
.sale-form-btn-wrap {
|
||||
padding: 20px 20px 0;
|
||||
background-color: #fff;
|
||||
.btn-flex-wrap{
|
||||
.btn-flex-wrap {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 매물 목록
|
||||
.sale-form-bx{
|
||||
.sale-form-bx {
|
||||
margin-bottom: 14px;
|
||||
&:last-child{
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.sale-list-wrap{
|
||||
.sale-list-item{
|
||||
}
|
||||
.sale-list-wrap {
|
||||
.sale-list-item {
|
||||
padding-top: 14px;
|
||||
padding-bottom: 14px;
|
||||
border-bottom: 1px solid #ECECEC;
|
||||
border-bottom: 1px solid #ececec;
|
||||
cursor: pointer;
|
||||
&:first-child{
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
&:last-child{
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.sale-item-bx{
|
||||
.sale-item-date-bx{
|
||||
.sale-item-bx {
|
||||
.sale-item-date-bx {
|
||||
@include flex(0px);
|
||||
align-items: center;
|
||||
margin-bottom: 9px;
|
||||
.sale-item-num{
|
||||
.sale-item-num {
|
||||
position: relative;
|
||||
@include defaultFont($font-s-13, $font-w-400, $font-c);
|
||||
padding-right: 6px;
|
||||
&::after{
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
@ -124,31 +124,31 @@
|
||||
transform: translateY(-50%);
|
||||
width: 1px;
|
||||
height: 10px;
|
||||
background-color: #A2ABB8;
|
||||
background-color: #a2abb8;
|
||||
}
|
||||
}
|
||||
.sale-item-date{
|
||||
@include defaultFont($font-s-13, $font-w-400, #A2ABB8);
|
||||
.sale-item-date {
|
||||
@include defaultFont($font-s-13, $font-w-400, #a2abb8);
|
||||
padding-left: 6px;
|
||||
}
|
||||
}
|
||||
.sale-item-tit{
|
||||
.sale-item-tit {
|
||||
@include defaultFont($font-s-15, $font-w-500, $font-c);
|
||||
@include ellipsis(1);
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
.sale-item-customer{
|
||||
.sale-item-customer {
|
||||
@include defaultFont($font-s-13, $font-w-400, $font-c);
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
.sale-item-update-bx{
|
||||
.sale-item-update-bx {
|
||||
@include flex(0px);
|
||||
align-items: center;
|
||||
.sale-item-name{
|
||||
.sale-item-name {
|
||||
position: relative;
|
||||
@include defaultFont($font-s-13, $font-w-400, #A2ABB8);
|
||||
@include defaultFont($font-s-13, $font-w-400, #a2abb8);
|
||||
padding-right: 6px;
|
||||
&::after{
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
@ -156,176 +156,177 @@
|
||||
transform: translateY(-50%);
|
||||
width: 1px;
|
||||
height: 10px;
|
||||
background-color: #A2ABB8;
|
||||
background-color: #a2abb8;
|
||||
}
|
||||
}
|
||||
.sale-item-update{
|
||||
@include defaultFont($font-s-13, $font-w-400, #A2ABB8);
|
||||
.sale-item-update {
|
||||
@include defaultFont($font-s-13, $font-w-400, #a2abb8);
|
||||
padding-left: 6px;
|
||||
}
|
||||
}
|
||||
&.nodata{
|
||||
.sale-item-nodata{
|
||||
&.nodata {
|
||||
.sale-item-nodata {
|
||||
padding: 5px 0;
|
||||
text-align: center;
|
||||
@include defaultFont($font-s-15, $font-w-500, $font-c);
|
||||
}
|
||||
}
|
||||
}
|
||||
.sale-edit-btn{
|
||||
.sale-edit-btn {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
// 매물 상세
|
||||
.sale-data-table-wrap{
|
||||
.sale-data-table-wrap {
|
||||
padding: 24px;
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #ECECEC;
|
||||
border-top: 1px solid #ececec;
|
||||
}
|
||||
.sale-data-table{
|
||||
.sale-data-table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
tbody{
|
||||
tr{
|
||||
th{
|
||||
tbody {
|
||||
tr {
|
||||
th {
|
||||
@include defaultFont($font-s-13, $font-w-500, $font-c);
|
||||
vertical-align: top;
|
||||
padding: 5px 0;
|
||||
}
|
||||
td{
|
||||
td {
|
||||
@include defaultFont($font-s-13, $font-w-400, $font-c);
|
||||
padding: 5px 0 8px 14px;
|
||||
.data-down{
|
||||
.data-down {
|
||||
@include flex(8px);
|
||||
align-items: center;
|
||||
color: #1259CB;
|
||||
i{
|
||||
color: #1259cb;
|
||||
i {
|
||||
display: block;
|
||||
width: 8px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:first-child{
|
||||
th,td{
|
||||
&:first-child {
|
||||
th,
|
||||
td {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
&:last-child{
|
||||
th,td{
|
||||
&:last-child {
|
||||
th,
|
||||
td {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sale-detail-toggle-wrap{
|
||||
border-top: 1px solid #ECECEC;
|
||||
.sale-detail-toggle-wrap {
|
||||
border-top: 1px solid #ececec;
|
||||
}
|
||||
.sale-detail-toggle-bx{
|
||||
border-bottom: 1px solid #ECECEC;
|
||||
.sale-detail-toggle-bx {
|
||||
border-bottom: 1px solid #ececec;
|
||||
}
|
||||
.sale-detail-toggle-head{
|
||||
.sale-detail-toggle-head {
|
||||
@include flex(5px);
|
||||
padding: 14px 18px;
|
||||
background-color: $white-fff;
|
||||
cursor: pointer;
|
||||
.sale-detail-toggle-name{
|
||||
.sale-detail-toggle-name {
|
||||
@include defaultFont($font-s-13, $font-w-500, $font-c);
|
||||
}
|
||||
.sale-detail-toggle-btn-wrap{
|
||||
.sale-detail-toggle-btn-wrap {
|
||||
margin-left: auto;
|
||||
.sale-detail-toggle-btn{
|
||||
.sale-detail-toggle-btn {
|
||||
display: block;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
background: url(/assets/images/sub/sale_toggle_btn.svg)no-repeat center;
|
||||
background-size: cover
|
||||
background: url(/assets/images/sub/sale_toggle_btn.svg) no-repeat center;
|
||||
background-size: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
.sale-detail-toggle-cont{
|
||||
.sale-detail-toggle-cont {
|
||||
display: none;
|
||||
.sale-frame{
|
||||
.sale-frame {
|
||||
padding: 24px 20px;
|
||||
&:first-child{
|
||||
&:first-child {
|
||||
padding-top: 24px;
|
||||
}
|
||||
&:last-child{
|
||||
&:last-child {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sale-detail-toggle-bx{
|
||||
&.act{
|
||||
.sale-detail-toggle-head{
|
||||
background-color: #5F738E;
|
||||
.sale-detail-toggle-name{
|
||||
color: #fff
|
||||
.sale-detail-toggle-bx {
|
||||
&.act {
|
||||
.sale-detail-toggle-head {
|
||||
background-color: #5f738e;
|
||||
.sale-detail-toggle-name {
|
||||
color: #fff;
|
||||
}
|
||||
.sale-detail-toggle-btn-wrap{
|
||||
.sale-detail-toggle-btn{
|
||||
background: url(/assets/images/sub/sale_toggle_btn_white.svg)no-repeat center;
|
||||
.sale-detail-toggle-btn-wrap {
|
||||
.sale-detail-toggle-btn {
|
||||
background: url(/assets/images/sub/sale_toggle_btn_white.svg) no-repeat center;
|
||||
}
|
||||
}
|
||||
}
|
||||
.sale-detail-toggle-cont{
|
||||
.sale-detail-toggle-cont {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 매물 기본정보
|
||||
.form-flex{
|
||||
.form-flex {
|
||||
@include flex(5px);
|
||||
.form-bx{
|
||||
.form-bx {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
.form-btn{
|
||||
.form-btn {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
// 매물 전기 지붕정보
|
||||
.sale-roof-title{
|
||||
.sale-roof-title {
|
||||
@include defaultFont($font-s-15, $font-w-500, $font-c);
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 1px solid #2E3A59;
|
||||
border-bottom: 1px solid #2e3a59;
|
||||
}
|
||||
.data-check-wrap{
|
||||
.data-check-wrap {
|
||||
@include flex(10px);
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 12px;
|
||||
.radio-form-box,
|
||||
.check-form-box{
|
||||
.check-form-box {
|
||||
width: calc(50% - 5px);
|
||||
}
|
||||
&.mb0{
|
||||
&.mb0 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.data-input{
|
||||
&.flex{
|
||||
.data-input {
|
||||
&.flex {
|
||||
@include flex(8px);
|
||||
align-items: center;
|
||||
span{
|
||||
span {
|
||||
flex: none;
|
||||
@include defaultFont($font-s-13, $font-w-400, $font-c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 1:1 문의 common
|
||||
.inquiry-frame{
|
||||
.inquiry-frame {
|
||||
padding: 0 20px;
|
||||
}
|
||||
.badge{
|
||||
.badge {
|
||||
min-width: 60px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
@ -334,65 +335,64 @@
|
||||
text-align: center;
|
||||
font-size: $font-s-12;
|
||||
font-weight: $font-w-500;
|
||||
&.blue{
|
||||
color: #5497E9;
|
||||
background-color: #ECF5FF;
|
||||
&.blue {
|
||||
color: #5497e9;
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
&.orange{
|
||||
color: #F86A56;
|
||||
background-color: #FFEFED;
|
||||
&.orange {
|
||||
color: #f86a56;
|
||||
background-color: #ffefed;
|
||||
}
|
||||
&.block{
|
||||
&.block {
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
}
|
||||
// 1:1 문의 목록
|
||||
.inquiry-table-filter{
|
||||
.inquiry-table-filter {
|
||||
margin-bottom: 24px;
|
||||
.filter-check{
|
||||
.filter-check {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
.inquiry-list-tit{
|
||||
.inquiry-list-tit {
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #2E3A59;
|
||||
border-bottom: 1px solid #2e3a59;
|
||||
@include defaultFont($font-s-13, $font-w-400, $font-c);
|
||||
span{
|
||||
span {
|
||||
font-weight: $font-w-500;
|
||||
}
|
||||
}
|
||||
.inquiry-list{
|
||||
.inquiry-item{
|
||||
.inquiry-list {
|
||||
.inquiry-item {
|
||||
padding: 10px 0;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #ECECEC;
|
||||
&:last-child{
|
||||
border-bottom: 1px solid #ececec;
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.inquiry-item-bx{
|
||||
.inquiry-item-bx {
|
||||
position: relative;
|
||||
padding-right: 70px;
|
||||
.inquiry-item-category{
|
||||
.inquiry-item-category {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
span{
|
||||
span {
|
||||
position: relative;
|
||||
display: block;
|
||||
@include defaultFont($font-s-13, $font-w-400, $font-c);
|
||||
padding: 0 6px;
|
||||
&:first-child{
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
&:last-child{
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
&::before{
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&::before{
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
@ -400,26 +400,31 @@
|
||||
transform: translateY(-50%);
|
||||
width: 1px;
|
||||
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 ellipsis(1);
|
||||
margin-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
.inquiry-item-date{
|
||||
@include defaultFont($font-s-13, $font-w-400, #A2ABB8);
|
||||
.inquiry-item-date {
|
||||
@include defaultFont($font-s-13, $font-w-400, #a2abb8);
|
||||
}
|
||||
.inquiry-badge{
|
||||
.inquiry-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
&.nodata{
|
||||
&.nodata {
|
||||
padding-right: 0;
|
||||
.inquiry-item-nodata{
|
||||
.inquiry-item-nodata {
|
||||
padding: 10px 0;
|
||||
text-align: center;
|
||||
@include defaultFont($font-s-15, $font-w-500, $font-c);
|
||||
@ -430,42 +435,45 @@
|
||||
}
|
||||
|
||||
// 1:1문의 작성
|
||||
.inquiry-file-wrap{
|
||||
.textarea-form {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.inquiry-file-wrap {
|
||||
margin-top: 20px;
|
||||
.file-list-wrap{
|
||||
.file-list-wrap {
|
||||
margin-top: 14px;
|
||||
}
|
||||
}
|
||||
.file-list-tit{
|
||||
.file-list-tit {
|
||||
@include defaultFont($font-s-13, $font-w-500, $font-c);
|
||||
}
|
||||
.file-list{
|
||||
.file-list {
|
||||
margin-top: 14px;
|
||||
.file-item{
|
||||
border-top: 1px solid #EDEDED;
|
||||
.file-item {
|
||||
border-top: 1px solid #ededed;
|
||||
cursor: default;
|
||||
.file-item-bx{
|
||||
.file-item-bx {
|
||||
width: 100%;
|
||||
padding: 14px 0;
|
||||
@include flex(0px);
|
||||
align-items: center;
|
||||
.file-item-name{
|
||||
.file-item-name {
|
||||
@include ellipsis(1);
|
||||
@include defaultFont($font-s-13, $font-w-400, $font-c);
|
||||
padding-right: 10px;
|
||||
}
|
||||
.file-del{
|
||||
.file-del {
|
||||
flex: none;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
width: 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;
|
||||
}
|
||||
}
|
||||
&:last-child{
|
||||
.file-item-bx{
|
||||
&:last-child {
|
||||
.file-item-bx {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
@ -473,33 +481,33 @@
|
||||
}
|
||||
|
||||
// 1:1 문의 상세
|
||||
.inquiry-detail-data-table{
|
||||
.inquiry-detail-data-table {
|
||||
padding: 20px 0;
|
||||
border-bottom: 1px solid #ECECEC;
|
||||
border-bottom: 1px solid #ececec;
|
||||
}
|
||||
.inquiry-detail-data{
|
||||
.inquiry-detail-data {
|
||||
padding: 20px 0;
|
||||
border-bottom: 1px solid #2E3A59;
|
||||
border-bottom: 1px solid #2e3a59;
|
||||
margin-bottom: 24px;
|
||||
.inquiry-detail-category{
|
||||
.inquiry-detail-category {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 3px;
|
||||
span{
|
||||
span {
|
||||
position: relative;
|
||||
display: block;
|
||||
@include defaultFont($font-s-13, $font-w-400, $font-c);
|
||||
padding: 0 6px;
|
||||
&:first-child{
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
&:last-child{
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
&::before{
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&::before{
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
@ -507,152 +515,154 @@
|
||||
transform: translateY(-50%);
|
||||
width: 1px;
|
||||
height: 10px;
|
||||
background-color: #A2ABB8;
|
||||
background-color: #a2abb8;
|
||||
}
|
||||
}
|
||||
}
|
||||
.inquiry-detail-tit{
|
||||
.inquiry-detail-tit {
|
||||
@include defaultFont($font-s-15, $font-w-500, $font-c);
|
||||
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);
|
||||
white-space: pre-line;
|
||||
}
|
||||
}
|
||||
|
||||
// 1:1 문의 답변
|
||||
.inquiry-answer-wrap{
|
||||
.inquiry-answer-wrap {
|
||||
margin-top: 24px;
|
||||
|
||||
}
|
||||
.inquiry-answer-header{
|
||||
.inquiry-answer-header {
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #F86A56;
|
||||
border-bottom: 1px solid #ECECEC;
|
||||
.inquiry-answer-tit{
|
||||
@include defaultFont($font-s-14, $font-w-500, #F86A56);
|
||||
border-top: 1px solid #f86a56;
|
||||
border-bottom: 1px solid #ececec;
|
||||
.inquiry-answer-tit {
|
||||
@include defaultFont($font-s-14, $font-w-500, #f86a56);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.inquiry-answer-date{
|
||||
@include defaultFont($font-s-13, $font-w-400, #F86A56);
|
||||
.inquiry-answer-date {
|
||||
@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);
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
// 비밀번호 변경
|
||||
.border-frame{
|
||||
.border-frame {
|
||||
padding: 20px;
|
||||
border-top: 1px solid #ECECEC;
|
||||
border-bottom: 1px solid #ECECEC;
|
||||
border-top: 1px solid #ececec;
|
||||
border-bottom: 1px solid #ececec;
|
||||
background-color: #fff;
|
||||
margin-bottom: 10px;
|
||||
&:last-child{
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.pw-guide{
|
||||
.pw-guide-tit{
|
||||
@include defaultFont($font-s-16, $font-w-500, #1259CB);
|
||||
.pw-guide {
|
||||
.pw-guide-tit {
|
||||
@include defaultFont($font-s-16, $font-w-500, #1259cb);
|
||||
}
|
||||
.pw-guide-txt{
|
||||
@include defaultFont($font-s-13, $font-w-400, #417DDC);
|
||||
.pw-guide-txt {
|
||||
@include defaultFont($font-s-13, $font-w-400, #417ddc);
|
||||
}
|
||||
}
|
||||
|
||||
// 지붕재 적합성
|
||||
.compliance-icon{
|
||||
.compliance-icon {
|
||||
display: flex;
|
||||
}
|
||||
.compliance-check-wrap{
|
||||
.compliance-check-wrap {
|
||||
padding-top: 10px;
|
||||
}
|
||||
.compliance-check-bx{
|
||||
.compliance-check-bx {
|
||||
position: relative;
|
||||
padding: 14px 18px;
|
||||
border: 1px solid #EFEFEF;
|
||||
border: 1px solid #efefef;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
&:last-child{
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&.act{
|
||||
.bx-btn{
|
||||
&.act {
|
||||
.bx-btn {
|
||||
transform: rotate(0) !important;
|
||||
}
|
||||
.reference-list{
|
||||
display: block
|
||||
.reference-list {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
.check-name-wrap{
|
||||
.check-name-wrap {
|
||||
@include flex(0px);
|
||||
align-items: center;
|
||||
.check-name{
|
||||
.check-name {
|
||||
@include defaultFont($font-s-13, $font-w-500, $font-c);
|
||||
}
|
||||
.check-name-btn{
|
||||
.check-name-btn {
|
||||
padding-left: 5px;
|
||||
margin-left: auto;
|
||||
.bx-btn{
|
||||
.bx-btn {
|
||||
display: block;
|
||||
width: 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
.reference-list{
|
||||
.reference-list {
|
||||
display: none;
|
||||
margin-top: 10px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid #ECECEC;
|
||||
transition: all .15s ease-in-out;
|
||||
.reference-item{
|
||||
border-top: 1px solid #ececec;
|
||||
transition: all 0.15s ease-in-out;
|
||||
.reference-item {
|
||||
margin-bottom: 8px;
|
||||
padding-left: 14px;
|
||||
.reference-item-bx{
|
||||
.reference-item-bx {
|
||||
@include flex(10px);
|
||||
@include defaultFont($font-s-13, $font-w-400, $font-c);
|
||||
align-items: center;
|
||||
}
|
||||
&:last-child{
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
&.check{
|
||||
.reference-item{
|
||||
&.check {
|
||||
.reference-item {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.compliace-nosearch{
|
||||
.compliace-nosearch {
|
||||
padding: 30px 0;
|
||||
span{
|
||||
span {
|
||||
display: block;
|
||||
@include defaultFont($font-s-13, $font-w-400, $font-c);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.check-item-wrap{
|
||||
.check-item-wrap {
|
||||
@include flex(0px);
|
||||
align-items: center;
|
||||
}
|
||||
.compliance-icon-wrap{
|
||||
.compliance-icon-wrap {
|
||||
margin-left: auto;
|
||||
min-width: 44px;
|
||||
@include flex(0px);
|
||||
align-items: center;
|
||||
}
|
||||
.float-btn-wrap{
|
||||
.float-btn-wrap {
|
||||
position: sticky;
|
||||
bottom: 10px;
|
||||
left: 0;
|
||||
@ -660,14 +670,14 @@
|
||||
background-color: #fff;
|
||||
z-index: 9;
|
||||
}
|
||||
@media screen and (max-width: 360px){
|
||||
.btn-flex-wrap{
|
||||
@media screen and (max-width: 360px) {
|
||||
.btn-flex-wrap {
|
||||
flex-direction: column;
|
||||
}
|
||||
.data-check-wrap{
|
||||
.data-check-wrap {
|
||||
.radio-form-box,
|
||||
.check-form-box{
|
||||
.check-form-box {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,9 +26,7 @@ export interface SessionData {
|
||||
groupId: null
|
||||
storeLvl: null
|
||||
custCd: null
|
||||
builderId: null
|
||||
builderNo: null
|
||||
builderNm: null | string
|
||||
isLoggedIn: boolean
|
||||
role: string | null
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ export enum SUITABLE_HEAD_CODE {
|
||||
/* 지붕재 제조사명 */
|
||||
MANU_FT_CD = 'MANU_FT_CD',
|
||||
/* 지붕재 그룹 종류 */
|
||||
ROOF_MATL_GRP_CD = 'ROOF_MATL_GRP_CD',
|
||||
ROOF_MATERIAL_GROUP = 'ROOF_MATL_GRP_CD',
|
||||
/* 지붕재 종류 */
|
||||
ROOF_MT_CD = 'ROOF_MT_CD',
|
||||
/* 마운팅 브래킷 종류 */
|
||||
|
||||
@ -5,7 +5,6 @@ export type SurveyBasicInfo = {
|
||||
store: string | null
|
||||
storeId: string | null
|
||||
constructionPoint: string | null
|
||||
constructionPointId: string | null
|
||||
investigationDate: string | null
|
||||
buildingName: string | null
|
||||
customerName: string | null
|
||||
@ -18,7 +17,6 @@ export type SurveyBasicInfo = {
|
||||
regDt: Date
|
||||
uptDt: Date
|
||||
submissionTargetId: string | null
|
||||
submissionTargetNm: string | null
|
||||
srlNo: string | null //판매점IDyyMMdd000
|
||||
}
|
||||
|
||||
@ -70,7 +68,6 @@ export type SurveyBasicRequest = {
|
||||
store: string | null
|
||||
storeId: string | null
|
||||
constructionPoint: string | null
|
||||
constructionPointId: string | null
|
||||
investigationDate: string | null
|
||||
buildingName: string | null
|
||||
customerName: string | null
|
||||
@ -80,7 +77,6 @@ export type SurveyBasicRequest = {
|
||||
submissionStatus: boolean
|
||||
submissionDate: string | null
|
||||
submissionTargetId: string | null
|
||||
submissionTargetNm: string | null
|
||||
srlNo: string | null //판매점IDyyMMdd000
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user