feat(WEB): Vercel 배포 대응 및 타입 안정성 개선

- puppeteer → puppeteer-core + @sparticuz/chromium 전환 (Vercel 서버리스 호환)
- PDF 생성 API 로컬/Vercel 환경 분기 처리
- next.config.ts: ignoreBuildErrors false로 전환
- WorkOrder items에 orderNodeId/orderNodeName 필드 추가
- 결재선 데이터에 name/dept 필드 추가
- OrderSalesDetailView 타입 캐스팅 안전하게 수정
- vercel.json 설정 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-09 10:45:57 +09:00
parent f3b07ac875
commit f320ec7d37
20 changed files with 226 additions and 269 deletions

View File

@@ -40,6 +40,15 @@ API_KEY=your-secret-api-key-here
# 주의: 운영 환경에서는 반드시 false로 설정! # 주의: 운영 환경에서는 반드시 false로 설정!
NEXT_PUBLIC_DEV_TOOLBAR_ENABLED=false NEXT_PUBLIC_DEV_TOOLBAR_ENABLED=false
# ==============================================
# Puppeteer (로컬 PDF 생성용)
# ==============================================
# puppeteer-core는 Chromium을 번들하지 않으므로 로컬 Chrome 경로 필요
# macOS: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
# Linux: /usr/bin/google-chrome-stable
# Vercel에서는 @sparticuz/chromium이 자동 처리하므로 설정 불필요
PUPPETEER_EXECUTABLE_PATH=/Applications/Google Chrome.app/Contents/MacOS/Google Chrome
# ============================================== # ==============================================
# Development Notes # Development Notes
# ============================================== # ==============================================

View File

@@ -0,0 +1,81 @@
# Vercel 환경변수 설정 가이드
## 설정 위치
Vercel Dashboard → Project → Settings → Environment Variables
---
## 필수 환경변수
### 서버 전용 (Server-side Only)
| 변수명 | 설명 | 예시 값 | 환경 |
|--------|------|---------|------|
| `API_KEY` | 백엔드 API 인증 키 (X-API-KEY 헤더) | `42Jfwc6Ea...` | Production, Preview |
| `API_URL` | 백엔드 API 내부 URL (서버 사이드 전용) | `https://api.codebridge-x.com` | Production, Preview |
> `API_KEY`는 절대 `NEXT_PUBLIC_` 접두사를 붙이지 말 것. 클라이언트에 노출되면 안 됨.
### 클라이언트 공개 (NEXT_PUBLIC_*)
| 변수명 | 설명 | Production 값 | Preview 값 |
|--------|------|--------------|------------|
| `NEXT_PUBLIC_API_URL` | 프론트엔드에서 호출하는 API URL | `https://api.codebridge-x.com` | `https://api.codebridge-x.com` |
| `NEXT_PUBLIC_FRONTEND_URL` | 프론트엔드 자체 URL (CORS 용) | `https://dev.codebridge-x.com` | Vercel 자동 할당 URL |
| `NEXT_PUBLIC_AUTH_MODE` | 인증 모드 | `sanctum` | `sanctum` |
| `NEXT_PUBLIC_GOOGLE_MAPS_API_KEY` | Google Maps API 키 | `AIzaSy...` | `AIzaSy...` |
| `NEXT_PUBLIC_DEV_TOOLBAR_ENABLED` | 개발 도구 툴바 | `false` | `true` |
### 선택 환경변수
| 변수명 | 설명 | 기본값 |
|--------|------|--------|
| `NEXT_PUBLIC_API_LOGGING` | API 로깅 활성화 | 미설정 (비활성) |
| `NEXT_PUBLIC_APP_VERSION` | 앱 버전 표시 | 미설정 |
| `PUPPETEER_EXECUTABLE_PATH` | Chromium 경로 (Vercel에서는 불필요) | 미설정 |
---
## 환경별 설정 방법
### 1. Vercel Dashboard에서 설정
1. [Vercel Dashboard](https://vercel.com) 접속
2. 프로젝트 선택 → **Settings****Environment Variables**
3. 각 변수 추가 시 적용 환경 선택:
- **Production**: 운영 배포에만 적용
- **Preview**: PR/브랜치 배포에 적용
- **Development**: `vercel dev` 로컬 실행 시 적용
### 2. 환경별 권장 설정
#### Production
```
API_KEY=<실제 운영 API 키>
API_URL=https://api.codebridge-x.com
NEXT_PUBLIC_API_URL=https://api.codebridge-x.com
NEXT_PUBLIC_FRONTEND_URL=https://<운영 도메인>
NEXT_PUBLIC_AUTH_MODE=sanctum
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=<Google Maps 키>
NEXT_PUBLIC_DEV_TOOLBAR_ENABLED=false
```
#### Preview
```
API_KEY=<개발용 API 키>
API_URL=https://api.codebridge-x.com
NEXT_PUBLIC_API_URL=https://api.codebridge-x.com
NEXT_PUBLIC_FRONTEND_URL=<Vercel Preview URL - 자동>
NEXT_PUBLIC_AUTH_MODE=sanctum
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=<Google Maps 키>
NEXT_PUBLIC_DEV_TOOLBAR_ENABLED=true
```
---
## 주의사항
1. **API_KEY 보안**: Sensitive 체크박스를 반드시 활성화하여 Dashboard에서도 값이 마스킹되도록 설정
2. **NEXT_PUBLIC_ 접두사**: 이 접두사가 붙은 변수는 클라이언트 번들에 포함되므로 민감 정보 절대 금지
3. **VERCEL 환경변수**: `VERCEL=1`은 Vercel이 자동 주입하므로 별도 설정 불필요 (PDF 생성 분기에 사용)
4. **NODE_ENV**: Vercel이 자동 설정 (`production` for Production, `development` for Preview)

View File

@@ -6,7 +6,7 @@ const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts');
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
reactStrictMode: false, // 🧪 TEST: Strict Mode 비활성화로 중복 요청 테스트 reactStrictMode: false, // 🧪 TEST: Strict Mode 비활성화로 중복 요청 테스트
turbopack: {}, // ✅ CRITICAL: Next.js 15 + next-intl compatibility turbopack: {}, // ✅ CRITICAL: Next.js 15 + next-intl compatibility
serverExternalPackages: ['puppeteer'], // puppeteer는 Node.js 전용 - Webpack 번들 제외 serverExternalPackages: ['puppeteer-core', '@sparticuz/chromium'], // Vercel 서버리스 PDF 생성용 - Webpack 번들 제외
images: { images: {
remotePatterns: [ remotePatterns: [
{ {
@@ -21,13 +21,9 @@ const nextConfig: NextConfig = {
}, },
}, },
typescript: { typescript: {
// ⚠️ WARNING: This allows production builds to complete even with TypeScript errors ignoreBuildErrors: false,
// Only use during development. Remove for production deployments.
ignoreBuildErrors: true,
}, },
eslint: { eslint: {
// ⚠️ WARNING: Temporarily ignore ESLint during builds for migration
// TODO: Fix ESLint errors after migration is complete
ignoreDuringBuilds: true, ignoreDuringBuilds: true,
}, },
// Capacitor 패키지는 모바일 앱 전용 - 웹 빌드에서 제외 // Capacitor 패키지는 모바일 앱 전용 - 웹 빌드에서 제외

305
package-lock.json generated
View File

@@ -29,6 +29,7 @@
"@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8", "@radix-ui/react-tooltip": "^1.2.8",
"@sparticuz/chromium": "^143.0.4",
"@tiptap/extension-image": "^3.13.0", "@tiptap/extension-image": "^3.13.0",
"@tiptap/extension-link": "^3.13.0", "@tiptap/extension-link": "^3.13.0",
"@tiptap/extension-placeholder": "^3.13.0", "@tiptap/extension-placeholder": "^3.13.0",
@@ -48,7 +49,7 @@
"lucide-react": "^0.552.0", "lucide-react": "^0.552.0",
"next": "^15.5.9", "next": "^15.5.9",
"next-intl": "^4.4.0", "next-intl": "^4.4.0",
"puppeteer": "^23.11.1", "puppeteer-core": "^24.37.2",
"react": "^19.2.3", "react": "^19.2.3",
"react-day-picker": "^9.11.1", "react-day-picker": "^9.11.1",
"react-dom": "^19.2.3", "react-dom": "^19.2.3",
@@ -87,29 +88,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/@babel/code-frame": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz",
"integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==",
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.28.5",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.28.6", "version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
@@ -1536,18 +1514,17 @@
} }
}, },
"node_modules/@puppeteer/browsers": { "node_modules/@puppeteer/browsers": {
"version": "2.6.1", "version": "2.12.0",
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.6.1.tgz", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.12.0.tgz",
"integrity": "sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==", "integrity": "sha512-Xuq42yxcQJ54ti8ZHNzF5snFvtpgXzNToJ1bXUGQRaiO8t+B6UM8sTUJfvV+AJnqtkJU/7hdy6nbKyA12aHtRw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"debug": "^4.4.0", "debug": "^4.4.3",
"extract-zip": "^2.0.1", "extract-zip": "^2.0.1",
"progress": "^2.0.3", "progress": "^2.0.3",
"proxy-agent": "^6.5.0", "proxy-agent": "^6.5.0",
"semver": "^7.6.3", "semver": "^7.7.3",
"tar-fs": "^3.0.6", "tar-fs": "^3.1.1",
"unbzip2-stream": "^1.4.3",
"yargs": "^17.7.2" "yargs": "^17.7.2"
}, },
"bin": { "bin": {
@@ -2862,6 +2839,19 @@
"integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==", "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@sparticuz/chromium": {
"version": "143.0.4",
"resolved": "https://registry.npmjs.org/@sparticuz/chromium/-/chromium-143.0.4.tgz",
"integrity": "sha512-/6I7uQTRhRDD2/gGPQ1Gkf+Dqk0RYDACPJDZfSzz0OWk4JmUTonNHPXbrn6UIklOHlnDLf8xAAzkOZKB/cJpLA==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.11",
"tar-fs": "^3.1.1"
},
"engines": {
"node": ">=20.11.0"
}
},
"node_modules/@standard-schema/spec": { "node_modules/@standard-schema/spec": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
@@ -4977,26 +4967,6 @@
"node": ">= 0.6.0" "node": ">= 0.6.0"
} }
}, },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/basic-ftp": { "node_modules/basic-ftp": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz", "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz",
@@ -5029,30 +4999,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/buffer-crc32": { "node_modules/buffer-crc32": {
"version": "0.2.13", "version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
@@ -5116,6 +5062,7 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@@ -5192,22 +5139,22 @@
} }
}, },
"node_modules/chromium-bidi": { "node_modules/chromium-bidi": {
"version": "0.11.0", "version": "13.1.1",
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.11.0.tgz", "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-13.1.1.tgz",
"integrity": "sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA==", "integrity": "sha512-zB9MpoPd7VJwjowQqiW3FKOvQwffFMjQ8Iejp5ZW+sJaKLRhZX1sTxzl3Zt22TDB4zP0OOqs8lRoY7eAW5geyQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"mitt": "3.0.1", "mitt": "^3.0.1",
"zod": "3.23.8" "zod": "^3.24.1"
}, },
"peerDependencies": { "peerDependencies": {
"devtools-protocol": "*" "devtools-protocol": "*"
} }
}, },
"node_modules/chromium-bidi/node_modules/zod": { "node_modules/chromium-bidi/node_modules/zod": {
"version": "3.23.8", "version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
@@ -5316,32 +5263,6 @@
"url": "https://opencollective.com/core-js" "url": "https://opencollective.com/core-js"
} }
}, },
"node_modules/cosmiconfig": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
"integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==",
"license": "MIT",
"dependencies": {
"env-paths": "^2.2.1",
"import-fresh": "^3.3.0",
"js-yaml": "^4.1.0",
"parse-json": "^5.2.0"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/d-fischer"
},
"peerDependencies": {
"typescript": ">=4.9.5"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/crc-32": { "node_modules/crc-32": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
@@ -5700,9 +5621,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/devtools-protocol": { "node_modules/devtools-protocol": {
"version": "0.0.1367902", "version": "0.0.1566079",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1566079.tgz",
"integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==", "integrity": "sha512-MJfAEA1UfVhSs7fbSQOG4czavUp1ajfg6prlAN0+cmfa2zNjaIbvq8VneP7do1WAQQIvgNJWSMeP6UyI90gIlQ==",
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/doctrine": { "node_modules/doctrine": {
@@ -5791,24 +5712,6 @@
"url": "https://github.com/fb55/entities?sponsor=1" "url": "https://github.com/fb55/entities?sponsor=1"
} }
}, },
"node_modules/env-paths": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/error-ex": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
"integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.2.1"
}
},
"node_modules/es-abstract": { "node_modules/es-abstract": {
"version": "1.24.1", "version": "1.24.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz",
@@ -6674,6 +6577,26 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/for-each": { "node_modules/for-each": {
"version": "0.3.5", "version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@@ -7078,26 +7001,6 @@
"node": ">= 14" "node": ">= 14"
} }
}, },
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/ignore": { "node_modules/ignore": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -7122,6 +7025,7 @@
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"parent-module": "^1.0.0", "parent-module": "^1.0.0",
@@ -7213,12 +7117,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
"license": "MIT"
},
"node_modules/is-async-function": { "node_modules/is-async-function": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
@@ -7655,12 +7553,14 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
@@ -7676,12 +7576,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"license": "MIT"
},
"node_modules/json-schema-traverse": { "node_modules/json-schema-traverse": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -8047,12 +7941,6 @@
"url": "https://opencollective.com/parcel" "url": "https://opencollective.com/parcel"
} }
}, },
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"license": "MIT"
},
"node_modules/linkify-it": { "node_modules/linkify-it": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
@@ -8702,6 +8590,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"callsites": "^3.0.0" "callsites": "^3.0.0"
@@ -8710,24 +8599,6 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
"json-parse-even-better-errors": "^2.3.0",
"lines-and-columns": "^1.1.6"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/path-exists": { "node_modules/path-exists": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -9150,40 +9021,19 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/puppeteer": {
"version": "23.11.1",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.11.1.tgz",
"integrity": "sha512-53uIX3KR5en8l7Vd8n5DUv90Ae9QDQsyIthaUFVzwV6yU750RjqRznEtNMBT20VthqAdemnJN+hxVdmMHKt7Zw==",
"deprecated": "< 24.15.0 is no longer supported",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@puppeteer/browsers": "2.6.1",
"chromium-bidi": "0.11.0",
"cosmiconfig": "^9.0.0",
"devtools-protocol": "0.0.1367902",
"puppeteer-core": "23.11.1",
"typed-query-selector": "^2.12.0"
},
"bin": {
"puppeteer": "lib/cjs/puppeteer/node/cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/puppeteer-core": { "node_modules/puppeteer-core": {
"version": "23.11.1", "version": "24.37.2",
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.11.1.tgz", "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.37.2.tgz",
"integrity": "sha512-3HZ2/7hdDKZvZQ7dhhITOUg4/wOrDRjyK2ZBllRB0ZCOi9u0cwq1ACHDjBB+nX+7+kltHjQvBRdeY7+W0T+7Gg==", "integrity": "sha512-nN8qwE3TGF2vA/+xemPxbesntTuqD9vCGOiZL2uh8HES3pPzLX20MyQjB42dH2rhQ3W3TljZ4ZaKZ0yX/abQuw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@puppeteer/browsers": "2.6.1", "@puppeteer/browsers": "2.12.0",
"chromium-bidi": "0.11.0", "chromium-bidi": "13.1.1",
"debug": "^4.4.0", "debug": "^4.4.3",
"devtools-protocol": "0.0.1367902", "devtools-protocol": "0.0.1566079",
"typed-query-selector": "^2.12.0", "typed-query-selector": "^2.12.0",
"ws": "^8.18.0" "webdriver-bidi-protocol": "0.4.0",
"ws": "^8.19.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@@ -9523,6 +9373,7 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=4" "node": ">=4"
@@ -10277,12 +10128,6 @@
"utrie": "^1.0.2" "utrie": "^1.0.2"
} }
}, },
"node_modules/through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
"license": "MIT"
},
"node_modules/tiny-invariant": { "node_modules/tiny-invariant": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
@@ -10517,16 +10362,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/unbzip2-stream": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
"integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
"license": "MIT",
"dependencies": {
"buffer": "^5.2.1",
"through": "^2.3.8"
}
},
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "6.21.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
@@ -10695,6 +10530,12 @@
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/webdriver-bidi-protocol": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.0.tgz",
"integrity": "sha512-U9VIlNRrq94d1xxR9JrCEAx5Gv/2W7ERSv8oWRoNe/QYbfccS0V3h/H6qeNeCRJxXGMhhnkqvwNrvPAYeuP9VA==",
"license": "Apache-2.0"
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@@ -35,6 +35,7 @@
"@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8", "@radix-ui/react-tooltip": "^1.2.8",
"@sparticuz/chromium": "^143.0.4",
"@tiptap/extension-image": "^3.13.0", "@tiptap/extension-image": "^3.13.0",
"@tiptap/extension-link": "^3.13.0", "@tiptap/extension-link": "^3.13.0",
"@tiptap/extension-placeholder": "^3.13.0", "@tiptap/extension-placeholder": "^3.13.0",
@@ -54,7 +55,7 @@
"lucide-react": "^0.552.0", "lucide-react": "^0.552.0",
"next": "^15.5.9", "next": "^15.5.9",
"next-intl": "^4.4.0", "next-intl": "^4.4.0",
"puppeteer": "^23.11.1", "puppeteer-core": "^24.37.2",
"react": "^19.2.3", "react": "^19.2.3",
"react-day-picker": "^9.11.1", "react-day-picker": "^9.11.1",
"react-dom": "^19.2.3", "react-dom": "^19.2.3",

View File

@@ -210,9 +210,9 @@ const createQmsMockWorkOrder = (subType?: string): WorkOrder => ({
shutterCount: 5, shutterCount: 5,
department: '생산부', department: '생산부',
items: [ items: [
{ id: '1', no: 1, status: 'completed', productName: '와이어 스크린', floorCode: '1층/FSS-01', specification: '3000×2500', quantity: 2, unit: 'EA' }, { id: '1', no: 1, status: 'completed', productName: '와이어 스크린', floorCode: '1층/FSS-01', specification: '3000×2500', quantity: 2, unit: 'EA', orderNodeId: null, orderNodeName: '' },
{ id: '2', no: 2, status: 'in_progress', productName: '메쉬 스크린', floorCode: '2층/FSS-03', specification: '3000×2500', quantity: 3, unit: 'EA' }, { id: '2', no: 2, status: 'in_progress', productName: '메쉬 스크린', floorCode: '2층/FSS-03', specification: '3000×2500', quantity: 3, unit: 'EA', orderNodeId: null, orderNodeName: '' },
{ id: '3', no: 3, status: 'waiting', productName: '광폭 와이어', floorCode: '3층/FSS-05', specification: '12000×4500', quantity: 1, unit: 'EA' }, { id: '3', no: 3, status: 'waiting', productName: '광폭 와이어', floorCode: '3층/FSS-05', specification: '12000×4500', quantity: 1, unit: 'EA', orderNodeId: null, orderNodeName: '' },
], ],
currentStep: 2, currentStep: 2,
issues: [], issues: [],
@@ -314,7 +314,7 @@ export const InspectionModalV2 = ({
// 저장된 측정값을 initialValues로 변환 // 저장된 측정값을 initialValues로 변환
const docData = result.resolveData?.document?.data; const docData = result.resolveData?.document?.data;
if (docData && docData.length > 0) { if (docData && docData.length > 0) {
const values = parseSavedDataToInitialValues(tmpl, docData); const values = parseSavedDataToInitialValues(tmpl, docData.map((d: { field_key: string; field_value?: string | null }) => ({ field_key: d.field_key, field_value: d.field_value ?? null })));
setImportInitialValues(values); setImportInitialValues(values);
} else { } else {
setImportInitialValues(undefined); setImportInitialValues(undefined);

View File

@@ -13,6 +13,7 @@ export const MOCK_WORK_ORDER: WorkOrder = {
projectName: '강남 아파트 단지', projectName: '강남 아파트 단지',
assignees: ['김작업', '이생산'], assignees: ['김작업', '이생산'],
quantity: 5, quantity: 5,
shutterCount: 3,
dueDate: '2024-10-05', dueDate: '2024-10-05',
priority: 1, priority: 1,
status: 'inProgress', status: 'inProgress',

View File

@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import puppeteer from 'puppeteer'; import puppeteer from 'puppeteer-core';
import chromium from '@sparticuz/chromium';
/** /**
* PDF 생성 API * PDF 생성 API
@@ -35,17 +36,20 @@ export async function POST(request: NextRequest) {
); );
} }
// Puppeteer 브라우저 실행 (Docker Alpine에서는 시스템 Chromium 사용) // 로컬 개발 vs Vercel 환경 분기
const isVercel = process.env.VERCEL === '1';
const browser = await puppeteer.launch({ const browser = await puppeteer.launch({
headless: true, args: isVercel ? chromium.args : [
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || undefined,
args: [
'--no-sandbox', '--no-sandbox',
'--disable-setuid-sandbox', '--disable-setuid-sandbox',
'--disable-dev-shm-usage', '--disable-dev-shm-usage',
'--disable-gpu', '--disable-gpu',
'--disable-software-rasterizer', '--disable-software-rasterizer',
], ],
executablePath: isVercel
? await chromium.executablePath()
: process.env.PUPPETEER_EXECUTABLE_PATH || '/usr/bin/google-chrome-stable',
headless: true,
}); });
const page = await browser.newPage(); const page = await browser.newPage();

View File

@@ -1325,10 +1325,10 @@ export async function getInspectionTemplate(params: {
}, },
// 결재선 데이터 // 결재선 데이터
approvalLines: [ approvalLines: [
{ id: 1, role: '작성', sortOrder: 1 }, { id: 1, name: '', dept: '', role: '작성', sortOrder: 1 },
{ id: 2, role: '검토', sortOrder: 2 }, { id: 2, name: '', dept: '', role: '검토', sortOrder: 2 },
{ id: 3, role: '승인', sortOrder: 3 }, { id: 3, name: '', dept: '', role: '승인', sortOrder: 3 },
{ id: 4, role: '승인', sortOrder: 4 }, { id: 4, name: '', dept: '', role: '승인', sortOrder: 4 },
], ],
}, },
inspectionItems: [ inspectionItems: [

View File

@@ -133,16 +133,16 @@ function OrderNodeCard({ node, depth = 0 }: { node: OrderNode; depth?: number })
)} )}
<MapPin className="h-4 w-4 text-blue-500" /> <MapPin className="h-4 w-4 text-blue-500" />
<span className="font-semibold text-sm">{node.name}</span> <span className="font-semibold text-sm">{node.name}</span>
{options.product_name && ( {options.product_name ? (
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">
({options.product_name as string}) ({String(options.product_name)})
</span> </span>
)} ) : null}
{(options.open_width || options.open_height) && ( {(options.open_width || options.open_height) ? (
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">
{options.open_width as string}x{options.open_height as string}mm {String(options.open_width ?? '')}x{String(options.open_height ?? '')}mm
</span> </span>
)} ) : null}
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<BadgeSm className={statusConfig.className}> <BadgeSm className={statusConfig.className}>

View File

@@ -231,6 +231,8 @@ export function WorkOrderDetail({ orderId }: WorkOrderDetailProps) {
specification: '8,260 X 8,350 mm', specification: '8,260 X 8,350 mm',
quantity: 500, quantity: 500,
unit: 'm', unit: 'm',
orderNodeId: null,
orderNodeName: '',
}, },
{ {
id: 'mock-2', id: 'mock-2',
@@ -241,6 +243,8 @@ export function WorkOrderDetail({ orderId }: WorkOrderDetailProps) {
specification: '1,200 X 2,400 mm', specification: '1,200 X 2,400 mm',
quantity: 100, quantity: 100,
unit: 'EA', unit: 'EA',
orderNodeId: null,
orderNodeName: '',
}, },
]; ];
} }

View File

@@ -164,6 +164,8 @@ export function WorkOrderEdit({ orderId }: WorkOrderEditProps) {
specification: '8,260 X 8,350 mm', specification: '8,260 X 8,350 mm',
quantity: 500, quantity: 500,
unit: 'm', unit: 'm',
orderNodeId: null,
orderNodeName: '',
}, },
{ {
id: 'mock-2', id: 'mock-2',
@@ -174,6 +176,8 @@ export function WorkOrderEdit({ orderId }: WorkOrderEditProps) {
specification: '1,200 X 2,400 mm', specification: '1,200 X 2,400 mm',
quantity: 100, quantity: 100,
unit: 'EA', unit: 'EA',
orderNodeId: null,
orderNodeName: '',
}, },
]); ]);
} else { } else {

View File

@@ -96,9 +96,9 @@ export function InspectionReportModal({
shutterCount: 12, shutterCount: 12,
department: '생산부', department: '생산부',
items: [ items: [
{ id: '1', no: 1, status: 'in_progress', productName: '와이어 스크린', floorCode: '1층/FSS-01', specification: '8,260 X 8,350', quantity: 2, unit: 'EA' }, { id: '1', no: 1, status: 'in_progress', productName: '와이어 스크린', floorCode: '1층/FSS-01', specification: '8,260 X 8,350', quantity: 2, unit: 'EA', orderNodeId: null, orderNodeName: '' },
{ id: '2', no: 2, status: 'waiting', productName: '메쉬 스크린', floorCode: '2층/FSS-03', specification: '6,400 X 5,200', quantity: 4, unit: 'EA' }, { id: '2', no: 2, status: 'waiting', productName: '메쉬 스크린', floorCode: '2층/FSS-03', specification: '6,400 X 5,200', quantity: 4, unit: 'EA', orderNodeId: null, orderNodeName: '' },
{ id: '3', no: 3, status: 'completed', productName: '광폭 와이어', floorCode: '3층/FSS-05', specification: '12,000 X 4,500', quantity: 1, unit: 'EA' }, { id: '3', no: 3, status: 'completed', productName: '광폭 와이어', floorCode: '3층/FSS-05', specification: '12,000 X 4,500', quantity: 1, unit: 'EA', orderNodeId: null, orderNodeName: '' },
], ],
currentStep: 2, currentStep: 2,
issues: [], issues: [],

View File

@@ -60,9 +60,9 @@ export function WorkLogModal({ open, onOpenChange, workOrderId, processType }: W
shutterCount: 12, shutterCount: 12,
department: '생산부', department: '생산부',
items: [ items: [
{ id: '1', no: 1, status: 'in_progress', productName: '와이어 스크린', floorCode: '1층/FSS-01', specification: '8,260 X 8,350', quantity: 2, unit: 'EA' }, { id: '1', no: 1, status: 'in_progress', productName: '와이어 스크린', floorCode: '1층/FSS-01', specification: '8,260 X 8,350', quantity: 2, unit: 'EA', orderNodeId: null, orderNodeName: '' },
{ id: '2', no: 2, status: 'waiting', productName: '메쉬 스크린', floorCode: '2층/FSS-03', specification: '6,400 X 5,200', quantity: 4, unit: 'EA' }, { id: '2', no: 2, status: 'waiting', productName: '메쉬 스크린', floorCode: '2층/FSS-03', specification: '6,400 X 5,200', quantity: 4, unit: 'EA', orderNodeId: null, orderNodeName: '' },
{ id: '3', no: 3, status: 'completed', productName: '광폭 와이어', floorCode: '3층/FSS-05', specification: '12,000 X 4,500', quantity: 1, unit: 'EA' }, { id: '3', no: 3, status: 'completed', productName: '광폭 와이어', floorCode: '3층/FSS-05', specification: '12,000 X 4,500', quantity: 1, unit: 'EA', orderNodeId: null, orderNodeName: '' },
], ],
currentStep: 2, currentStep: 2,
issues: [], issues: [],

View File

@@ -663,6 +663,7 @@ export default function WorkerScreen() {
projectName: '-', projectName: '-',
assignees: [], assignees: [],
quantity: mockItem.quantity, quantity: mockItem.quantity,
shutterCount: 0,
dueDate: '', dueDate: '',
priority: 5, priority: 5,
status: 'waiting', status: 'waiting',
@@ -789,6 +790,7 @@ export default function WorkerScreen() {
projectName: '-', projectName: '-',
assignees: [], assignees: [],
quantity: mockItem.quantity, quantity: mockItem.quantity,
shutterCount: 0,
dueDate: '', dueDate: '',
priority: 5, priority: 5,
status: 'waiting', status: 'waiting',
@@ -814,6 +816,7 @@ export default function WorkerScreen() {
projectName: '-', projectName: '-',
assignees: [], assignees: [],
quantity: item.quantity, quantity: item.quantity,
shutterCount: 0,
dueDate: '', dueDate: '',
priority: 5, priority: 5,
status: 'waiting', status: 'waiting',

View File

@@ -154,9 +154,10 @@ export function LocationDetailPanel({
Object.entries(subtotals).forEach(([key, value]) => { Object.entries(subtotals).forEach(([key, value]) => {
if (typeof value === "object" && value !== null) { if (typeof value === "object" && value !== null) {
const obj = value as { name?: string };
tabs.push({ tabs.push({
value: key, value: key,
label: value.name || key, label: obj.name || key,
}); });
} }
}); });

View File

@@ -10,6 +10,7 @@
import React from 'react'; import React from 'react';
import type { QuoteFormDataV2 } from './QuoteRegistrationV2'; import type { QuoteFormDataV2 } from './QuoteRegistrationV2';
import type { BomCalculationResultItem } from './types';
// 양식 타입 // 양식 타입
type TemplateType = 'vendor' | 'calculation'; type TemplateType = 'vendor' | 'calculation';
@@ -327,7 +328,7 @@ export function QuotePreviewContent({
{/* BOM 품목 상세 */} {/* BOM 품목 상세 */}
{bomItems.length > 0 ? ( {bomItems.length > 0 ? (
bomItems.map((item, itemIndex) => ( bomItems.map((item: BomCalculationResultItem, itemIndex: number) => (
<tr key={`${loc.id}-${itemIndex}`} className="border-b border-gray-200"> <tr key={`${loc.id}-${itemIndex}`} className="border-b border-gray-200">
<td className="border-r border-gray-300 px-2 py-1 text-center text-gray-500"> <td className="border-r border-gray-300 px-2 py-1 text-center text-gray-500">
{itemIndex === 0 ? locationSymbol : ''} {itemIndex === 0 ? locationSymbol : ''}

View File

@@ -108,10 +108,11 @@ export function QuoteSummaryPanel({
unitPrice: item.unit_price || 0, unitPrice: item.unit_price || 0,
totalPrice: item.total_price || 0, totalPrice: item.total_price || 0,
})); }));
const obj = value as { name?: string; count?: number; subtotal?: number };
result.push({ result.push({
label: value.name || key, label: obj.name || key,
count: value.count || 0, count: obj.count || 0,
amount: value.subtotal || 0, amount: obj.subtotal || 0,
items: groupItems, items: groupItems,
}); });
} else if (typeof value === "number") { } else if (typeof value === "number") {

View File

@@ -32,6 +32,7 @@ import type {
BomCalculationResultItem, BomCalculationResultItem,
BomCalculationResult, BomCalculationResult,
} from './types'; } from './types';
export type { BomCalculationResult, BomCalculationResultItem };
import { transformApiToFrontend, transformFrontendToApi } from './types'; import { transformApiToFrontend, transformFrontendToApi } from './types';
// ===== 페이지네이션 타입 ===== // ===== 페이지네이션 타입 =====

9
vercel.json Normal file
View File

@@ -0,0 +1,9 @@
{
"regions": ["icn1"],
"functions": {
"src/app/api/pdf/generate/route.ts": {
"memory": 1024,
"maxDuration": 30
}
}
}