feat: 수주/견적 기능 개선 및 PDF 생성 업데이트
- 수주 상세 뷰/수정 컴포넌트 개선 - 견적 위치 패널 업데이트 - PDF 생성 API 수정 - 레이아웃 및 공통코드 API 업데이트 - 패키지 의존성 업데이트
This commit is contained in:
@@ -6,6 +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 번들 제외
|
||||||
images: {
|
images: {
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
{
|
{
|
||||||
|
|||||||
166
package-lock.json
generated
166
package-lock.json
generated
@@ -47,7 +47,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": "^24.36.0",
|
"puppeteer": "^23.11.1",
|
||||||
"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",
|
||||||
@@ -1535,17 +1535,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@puppeteer/browsers": {
|
"node_modules/@puppeteer/browsers": {
|
||||||
"version": "2.11.1",
|
"version": "2.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.6.1.tgz",
|
||||||
"integrity": "sha512-YmhAxs7XPuxN0j7LJloHpfD1ylhDuFmmwMvfy/+6nBSrETT2ycL53LrhgPtR+f+GcPSybQVuQ5inWWu5MrWCpA==",
|
"integrity": "sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^4.4.3",
|
"debug": "^4.4.0",
|
||||||
"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.7.3",
|
"semver": "^7.6.3",
|
||||||
"tar-fs": "^3.1.1",
|
"tar-fs": "^3.0.6",
|
||||||
|
"unbzip2-stream": "^1.4.3",
|
||||||
"yargs": "^17.7.2"
|
"yargs": "^17.7.2"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -4944,6 +4945,26 @@
|
|||||||
"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",
|
||||||
@@ -4976,6 +4997,30 @@
|
|||||||
"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",
|
||||||
@@ -5115,22 +5160,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chromium-bidi": {
|
"node_modules/chromium-bidi": {
|
||||||
"version": "13.0.1",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-13.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.11.0.tgz",
|
||||||
"integrity": "sha512-c+RLxH0Vg2x2syS9wPw378oJgiJNXtYXUvnVAldUlt5uaHekn0CCU7gPksNgHjrH1qFhmjVXQj4esvuthuC7OQ==",
|
"integrity": "sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mitt": "^3.0.1",
|
"mitt": "3.0.1",
|
||||||
"zod": "^3.24.1"
|
"zod": "3.23.8"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"devtools-protocol": "*"
|
"devtools-protocol": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chromium-bidi/node_modules/zod": {
|
"node_modules/chromium-bidi/node_modules/zod": {
|
||||||
"version": "3.25.76",
|
"version": "3.23.8",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
|
||||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
@@ -5623,9 +5668,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/devtools-protocol": {
|
"node_modules/devtools-protocol": {
|
||||||
"version": "0.0.1551306",
|
"version": "0.0.1367902",
|
||||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1551306.tgz",
|
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz",
|
||||||
"integrity": "sha512-CFx8QdSim8iIv+2ZcEOclBKTQY6BI1IEDa7Tm9YkwAXzEWFndTEzpTo5jAUhSnq24IC7xaDw0wvGcm96+Y3PEg==",
|
"integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==",
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
"node_modules/doctrine": {
|
"node_modules/doctrine": {
|
||||||
@@ -7001,6 +7046,26 @@
|
|||||||
"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",
|
||||||
@@ -8312,6 +8377,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/next-intl/node_modules/@swc/helpers": {
|
||||||
|
"version": "0.5.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz",
|
||||||
|
"integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/next/node_modules/postcss": {
|
"node_modules/next/node_modules/postcss": {
|
||||||
"version": "8.4.31",
|
"version": "8.4.31",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||||
@@ -9043,17 +9119,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/puppeteer": {
|
"node_modules/puppeteer": {
|
||||||
"version": "24.36.0",
|
"version": "23.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.36.0.tgz",
|
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.11.1.tgz",
|
||||||
"integrity": "sha512-BD/VCyV/Uezvd6o7Fd1DmEJSxTzofAKplzDy6T9d4WbLTQ5A+06zY7VwO91ZlNU22vYE8sidVEsTpTrKc+EEnQ==",
|
"integrity": "sha512-53uIX3KR5en8l7Vd8n5DUv90Ae9QDQsyIthaUFVzwV6yU750RjqRznEtNMBT20VthqAdemnJN+hxVdmMHKt7Zw==",
|
||||||
|
"deprecated": "< 24.15.0 is no longer supported",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@puppeteer/browsers": "2.11.1",
|
"@puppeteer/browsers": "2.6.1",
|
||||||
"chromium-bidi": "13.0.1",
|
"chromium-bidi": "0.11.0",
|
||||||
"cosmiconfig": "^9.0.0",
|
"cosmiconfig": "^9.0.0",
|
||||||
"devtools-protocol": "0.0.1551306",
|
"devtools-protocol": "0.0.1367902",
|
||||||
"puppeteer-core": "24.36.0",
|
"puppeteer-core": "23.11.1",
|
||||||
"typed-query-selector": "^2.12.0"
|
"typed-query-selector": "^2.12.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -9064,18 +9141,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/puppeteer-core": {
|
"node_modules/puppeteer-core": {
|
||||||
"version": "24.36.0",
|
"version": "23.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.36.0.tgz",
|
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.11.1.tgz",
|
||||||
"integrity": "sha512-P3Ou0MAFDCQ0dK1d9F9+8jTrg6JvXjUacgG0YkJQP4kbEnUOGokSDEMmMId5ZhXD5HwsHM202E9VwEpEjWfwxg==",
|
"integrity": "sha512-3HZ2/7hdDKZvZQ7dhhITOUg4/wOrDRjyK2ZBllRB0ZCOi9u0cwq1ACHDjBB+nX+7+kltHjQvBRdeY7+W0T+7Gg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@puppeteer/browsers": "2.11.1",
|
"@puppeteer/browsers": "2.6.1",
|
||||||
"chromium-bidi": "13.0.1",
|
"chromium-bidi": "0.11.0",
|
||||||
"debug": "^4.4.3",
|
"debug": "^4.4.0",
|
||||||
"devtools-protocol": "0.0.1551306",
|
"devtools-protocol": "0.0.1367902",
|
||||||
"typed-query-selector": "^2.12.0",
|
"typed-query-selector": "^2.12.0",
|
||||||
"webdriver-bidi-protocol": "0.4.0",
|
"ws": "^8.18.0"
|
||||||
"ws": "^8.19.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -10169,6 +10245,12 @@
|
|||||||
"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",
|
||||||
@@ -10403,6 +10485,16 @@
|
|||||||
"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",
|
||||||
@@ -10571,12 +10663,6 @@
|
|||||||
"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",
|
||||||
|
|||||||
@@ -53,7 +53,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": "^24.36.0",
|
"puppeteer": "^23.11.1",
|
||||||
"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",
|
||||||
|
|||||||
@@ -522,15 +522,15 @@ function OrderListContent() {
|
|||||||
<TableCell>{order.expectedShipDate || "-"}</TableCell>
|
<TableCell>{order.expectedShipDate || "-"}</TableCell>
|
||||||
<TableCell>{order.orderDate || "-"}</TableCell>
|
<TableCell>{order.orderDate || "-"}</TableCell>
|
||||||
<TableCell>{order.client || "-"}</TableCell>
|
<TableCell>{order.client || "-"}</TableCell>
|
||||||
<TableCell>{(order as any).productName || "-"}</TableCell>
|
<TableCell>{order.productName || "-"}</TableCell>
|
||||||
<TableCell>{(order as any).receiver || "-"}</TableCell>
|
<TableCell>{order.receiver || "-"}</TableCell>
|
||||||
<TableCell className="max-w-[150px] truncate">{(order as any).receiverAddress || "-"}</TableCell>
|
<TableCell className="max-w-[150px] truncate">{order.receiverAddress || "-"}</TableCell>
|
||||||
<TableCell>{(order as any).receiverPlace || "-"}</TableCell>
|
<TableCell>{order.receiverPlace || "-"}</TableCell>
|
||||||
<TableCell>{order.deliveryMethodLabel || "-"}</TableCell>
|
<TableCell>{order.deliveryMethodLabel || "-"}</TableCell>
|
||||||
<TableCell>{(order as any).manager || "-"}</TableCell>
|
<TableCell>{order.manager || "-"}</TableCell>
|
||||||
<TableCell className="text-center">{(order as any).frameCount || "-"}</TableCell>
|
<TableCell className="text-center">{order.frameCount || "-"}</TableCell>
|
||||||
<TableCell>{getOrderStatusBadge(order.status)}</TableCell>
|
<TableCell>{getOrderStatusBadge(order.status)}</TableCell>
|
||||||
<TableCell className="max-w-[100px] truncate">{(order as any).remarks || "-"}</TableCell>
|
<TableCell className="max-w-[100px] truncate">{order.remarks || "-"}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,14 +35,16 @@ export async function POST(request: NextRequest) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Puppeteer 브라우저 실행
|
// Puppeteer 브라우저 실행 (Docker Alpine에서는 시스템 Chromium 사용)
|
||||||
const browser = await puppeteer.launch({
|
const browser = await puppeteer.launch({
|
||||||
headless: true,
|
headless: true,
|
||||||
|
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || undefined,
|
||||||
args: [
|
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',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import {
|
|||||||
updateOrder,
|
updateOrder,
|
||||||
type OrderStatus,
|
type OrderStatus,
|
||||||
} from "@/components/orders";
|
} from "@/components/orders";
|
||||||
|
import { getDeliveryMethodOptions, getCommonCodeOptions } from "@/lib/api/common-codes";
|
||||||
|
|
||||||
// 수정 폼 데이터
|
// 수정 폼 데이터
|
||||||
interface EditFormData {
|
interface EditFormData {
|
||||||
@@ -88,22 +89,11 @@ interface EditFormData {
|
|||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 배송방식 옵션
|
// 옵션 타입 정의
|
||||||
const DELIVERY_METHODS = [
|
interface SelectOption {
|
||||||
{ value: "direct", label: "직접배차" },
|
value: string;
|
||||||
{ value: "pickup", label: "상차" },
|
label: string;
|
||||||
{ value: "courier", label: "택배" },
|
}
|
||||||
{ value: "self", label: "직접수령" },
|
|
||||||
{ value: "freight", label: "화물" },
|
|
||||||
];
|
|
||||||
|
|
||||||
// 운임비용 옵션
|
|
||||||
const SHIPPING_COSTS = [
|
|
||||||
{ value: "free", label: "무료" },
|
|
||||||
{ value: "prepaid", label: "선불" },
|
|
||||||
{ value: "collect", label: "착불" },
|
|
||||||
{ value: "negotiable", label: "협의" },
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
// 상태 뱃지 헬퍼
|
// 상태 뱃지 헬퍼
|
||||||
@@ -141,6 +131,10 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
|
|||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [expandedProducts, setExpandedProducts] = useState<Set<string>>(new Set());
|
const [expandedProducts, setExpandedProducts] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
|
// 공통코드 옵션
|
||||||
|
const [deliveryMethods, setDeliveryMethods] = useState<SelectOption[]>([]);
|
||||||
|
const [shippingCosts, setShippingCosts] = useState<SelectOption[]>([]);
|
||||||
|
|
||||||
// 제품-부품 트리 토글
|
// 제품-부품 트리 토글
|
||||||
const toggleProduct = (key: string) => {
|
const toggleProduct = (key: string) => {
|
||||||
setExpandedProducts((prev) => {
|
setExpandedProducts((prev) => {
|
||||||
@@ -265,6 +259,24 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
|
|||||||
loadOrder();
|
loadOrder();
|
||||||
}, [orderId, router]);
|
}, [orderId, router]);
|
||||||
|
|
||||||
|
// 공통코드 옵션 로드
|
||||||
|
useEffect(() => {
|
||||||
|
async function loadCommonCodes() {
|
||||||
|
const [deliveryResult, shippingResult] = await Promise.all([
|
||||||
|
getDeliveryMethodOptions(),
|
||||||
|
getCommonCodeOptions('shipping_cost'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (deliveryResult.success && deliveryResult.data) {
|
||||||
|
setDeliveryMethods(deliveryResult.data);
|
||||||
|
}
|
||||||
|
if (shippingResult.success && shippingResult.data) {
|
||||||
|
setShippingCosts(shippingResult.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadCommonCodes();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
// V2 패턴: ?mode=view로 이동
|
// V2 패턴: ?mode=view로 이동
|
||||||
router.push(`/sales/order-management-sales/${orderId}?mode=view`);
|
router.push(`/sales/order-management-sales/${orderId}?mode=view`);
|
||||||
@@ -453,7 +465,7 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
|
|||||||
<SelectValue placeholder="선택" />
|
<SelectValue placeholder="선택" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{DELIVERY_METHODS.map((method) => (
|
{deliveryMethods.map((method) => (
|
||||||
<SelectItem key={method.value} value={method.value}>
|
<SelectItem key={method.value} value={method.value}>
|
||||||
{method.label}
|
{method.label}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
@@ -476,7 +488,7 @@ export function OrderSalesDetailEdit({ orderId }: OrderSalesDetailEditProps) {
|
|||||||
<SelectValue placeholder="선택" />
|
<SelectValue placeholder="선택" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{SHIPPING_COSTS.map((cost) => (
|
{shippingCosts.map((cost) => (
|
||||||
<SelectItem key={cost.value} value={cost.value}>
|
<SelectItem key={cost.value} value={cost.value}>
|
||||||
{cost.label}
|
{cost.label}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
|
|||||||
@@ -354,7 +354,7 @@ export function OrderSalesDetailView({ orderId }: OrderSalesDetailViewProps) {
|
|||||||
<InfoItem label="출고예정일" value={order.expectedShipDate || "미정"} />
|
<InfoItem label="출고예정일" value={order.expectedShipDate || "미정"} />
|
||||||
<InfoItem label="납품요청일" value={order.deliveryRequestDate} />
|
<InfoItem label="납품요청일" value={order.deliveryRequestDate} />
|
||||||
<InfoItem label="배송방식" value={order.deliveryMethodLabel} />
|
<InfoItem label="배송방식" value={order.deliveryMethodLabel} />
|
||||||
<InfoItem label="운임비용" value={order.shippingCost} />
|
<InfoItem label="운임비용" value={order.shippingCostLabel} />
|
||||||
<InfoItem label="수신(반장/업체)" value={order.receiver} />
|
<InfoItem label="수신(반장/업체)" value={order.receiver} />
|
||||||
<InfoItem label="수신처 연락처" value={order.receiverContact} />
|
<InfoItem label="수신처 연락처" value={order.receiverContact} />
|
||||||
<InfoItem label="수신처 주소" value={order.address} />
|
<InfoItem label="수신처 주소" value={order.address} />
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ interface ApiOrder {
|
|||||||
delivery_date: string | null;
|
delivery_date: string | null;
|
||||||
delivery_method_code: string | null;
|
delivery_method_code: string | null;
|
||||||
delivery_method_label?: string; // API에서 조회한 배송방식 라벨
|
delivery_method_label?: string; // API에서 조회한 배송방식 라벨
|
||||||
|
shipping_cost_label?: string; // API에서 조회한 운임비용 라벨
|
||||||
received_at: string | null;
|
received_at: string | null;
|
||||||
memo: string | null;
|
memo: string | null;
|
||||||
remarks: string | null;
|
remarks: string | null;
|
||||||
@@ -231,11 +232,17 @@ export interface Order {
|
|||||||
remarks?: string;
|
remarks?: string;
|
||||||
note?: string;
|
note?: string;
|
||||||
items?: OrderItem[];
|
items?: OrderItem[];
|
||||||
|
// 목록 페이지용 추가 필드
|
||||||
|
productName?: string; // 제품명 (첫 번째 품목명)
|
||||||
|
receiverAddress?: string; // 수신주소
|
||||||
|
receiverPlace?: string; // 수신처 (전화번호)
|
||||||
|
frameCount?: number; // 틀수 (수량)
|
||||||
// 상세 페이지용 추가 필드
|
// 상세 페이지용 추가 필드
|
||||||
manager?: string; // 담당자
|
manager?: string; // 담당자
|
||||||
contact?: string; // 연락처 (client_contact)
|
contact?: string; // 연락처 (client_contact)
|
||||||
deliveryRequestDate?: string; // 납품요청일
|
deliveryRequestDate?: string; // 납품요청일
|
||||||
shippingCost?: string; // 운임비용
|
shippingCost?: string; // 운임비용 (코드)
|
||||||
|
shippingCostLabel?: string; // 운임비용 (라벨)
|
||||||
receiver?: string; // 수신자
|
receiver?: string; // 수신자
|
||||||
receiverContact?: string; // 수신처 연락처
|
receiverContact?: string; // 수신처 연락처
|
||||||
address?: string; // 수신처 주소
|
address?: string; // 수신처 주소
|
||||||
@@ -457,12 +464,19 @@ function transformApiToFrontend(apiData: ApiOrder): Order {
|
|||||||
memo: apiData.memo ?? undefined,
|
memo: apiData.memo ?? undefined,
|
||||||
remarks: apiData.remarks ?? undefined,
|
remarks: apiData.remarks ?? undefined,
|
||||||
note: apiData.note ?? undefined,
|
note: apiData.note ?? undefined,
|
||||||
items: apiData.items?.map(transformItemApiToFrontend) || [], // 상세 페이지용 추가 필드 (API에서 매핑)
|
items: apiData.items?.map(transformItemApiToFrontend) || [],
|
||||||
|
// 목록 페이지용 추가 필드
|
||||||
|
productName: apiData.items?.[0]?.item_name ?? undefined,
|
||||||
|
receiverAddress: apiData.options?.shipping_address ?? undefined,
|
||||||
|
receiverPlace: apiData.options?.receiver_contact ?? undefined,
|
||||||
|
frameCount: apiData.quantity ?? undefined,
|
||||||
|
// 상세 페이지용 추가 필드 (API에서 매핑)
|
||||||
manager: apiData.client?.manager_name ?? undefined,
|
manager: apiData.client?.manager_name ?? undefined,
|
||||||
contact: apiData.client_contact ?? apiData.client?.phone ?? undefined,
|
contact: apiData.client_contact ?? apiData.client?.phone ?? undefined,
|
||||||
deliveryRequestDate: apiData.delivery_date ?? undefined, // delivery_date를 공유
|
deliveryRequestDate: apiData.delivery_date ?? undefined, // delivery_date를 공유
|
||||||
// options JSON에서 추출
|
// options JSON에서 추출
|
||||||
shippingCost: apiData.options?.shipping_cost_code ?? undefined,
|
shippingCost: apiData.options?.shipping_cost_code ?? undefined,
|
||||||
|
shippingCostLabel: apiData.shipping_cost_label ?? undefined,
|
||||||
receiver: apiData.options?.receiver ?? undefined,
|
receiver: apiData.options?.receiver ?? undefined,
|
||||||
receiverContact: apiData.options?.receiver_contact ?? undefined,
|
receiverContact: apiData.options?.receiver_contact ?? undefined,
|
||||||
address: apiData.options?.shipping_address ?? undefined,
|
address: apiData.options?.shipping_address ?? undefined,
|
||||||
|
|||||||
@@ -402,7 +402,7 @@ export function LocationDetailPanel({
|
|||||||
<SelectContent>
|
<SelectContent>
|
||||||
{finishedGoods.map((fg) => (
|
{finishedGoods.map((fg) => (
|
||||||
<SelectItem key={fg.item_code} value={fg.item_code}>
|
<SelectItem key={fg.item_code} value={fg.item_code}>
|
||||||
{fg.item_code}
|
{fg.item_code} {fg.item_name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@@ -477,8 +477,8 @@ export function LocationDetailPanel({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 3행: 제작사이즈, 산출중량, 산출면적, 수량 */}
|
{/* 3행: 제작사이즈, 산출중량, 산출면적, 수량, 산출하기 */}
|
||||||
<div className="grid grid-cols-4 gap-3 text-sm pt-2 border-t border-gray-200">
|
<div className="grid grid-cols-5 gap-3 text-sm pt-2 border-t border-gray-200">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-xs text-gray-500">제작사이즈</span>
|
<span className="text-xs text-gray-500">제작사이즈</span>
|
||||||
<p className="font-semibold">
|
<p className="font-semibold">
|
||||||
@@ -503,6 +503,24 @@ export function LocationDetailPanel({
|
|||||||
: "-"}
|
: "-"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-xs text-gray-500">수량 (QTY)</span>
|
||||||
|
<QuantityInput
|
||||||
|
value={location.quantity}
|
||||||
|
onChange={(newQty) => {
|
||||||
|
if (!location || disabled) return;
|
||||||
|
// 수량 변경 시 totalPrice 재계산
|
||||||
|
const unitPrice = location.unitPrice || 0;
|
||||||
|
onUpdateLocation(location.id, {
|
||||||
|
quantity: newQty,
|
||||||
|
totalPrice: unitPrice * newQty,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="h-8 text-sm font-semibold"
|
||||||
|
min={1}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="flex items-end">
|
<div className="flex items-end">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => onCalculateLocation?.(location.id)}
|
onClick={() => onCalculateLocation?.(location.id)}
|
||||||
@@ -600,7 +618,41 @@ export function LocationDetailPanel({
|
|||||||
<TableCell className="text-center">
|
<TableCell className="text-center">
|
||||||
<QuantityInput
|
<QuantityInput
|
||||||
value={item.quantity}
|
value={item.quantity}
|
||||||
onChange={() => {}}
|
onChange={(newQty) => {
|
||||||
|
if (!location || disabled) return;
|
||||||
|
const existingBomResult = location.bomResult;
|
||||||
|
if (!existingBomResult) return;
|
||||||
|
|
||||||
|
// 해당 아이템 찾아서 수량 및 금액 업데이트
|
||||||
|
const updatedItems = (existingBomResult.items || []).map((bomItem: any, i: number) => {
|
||||||
|
if (bomItemsByTab[tab.value]?.[index] === bomItem) {
|
||||||
|
const newTotalPrice = (bomItem.unit_price || 0) * newQty;
|
||||||
|
return {
|
||||||
|
...bomItem,
|
||||||
|
quantity: newQty,
|
||||||
|
total_price: newTotalPrice,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return bomItem;
|
||||||
|
});
|
||||||
|
|
||||||
|
// grand_total 재계산
|
||||||
|
const newGrandTotal = updatedItems.reduce(
|
||||||
|
(sum: number, item: any) => sum + (item.total_price || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
// location 업데이트 (unitPrice, totalPrice 포함)
|
||||||
|
onUpdateLocation(location.id, {
|
||||||
|
unitPrice: newGrandTotal,
|
||||||
|
totalPrice: newGrandTotal * location.quantity,
|
||||||
|
bomResult: {
|
||||||
|
...existingBomResult,
|
||||||
|
items: updatedItems,
|
||||||
|
grand_total: newGrandTotal,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
className="w-14 h-7 text-center text-xs"
|
className="w-14 h-7 text-center text-xs"
|
||||||
min={1}
|
min={1}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ export function LocationListPanel({
|
|||||||
<SelectContent>
|
<SelectContent>
|
||||||
{finishedGoods.map((fg) => (
|
{finishedGoods.map((fg) => (
|
||||||
<SelectItem key={fg.item_code} value={fg.item_code}>
|
<SelectItem key={fg.item_code} value={fg.item_code}>
|
||||||
{fg.item_code}
|
{fg.item_code} {fg.item_name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|||||||
@@ -72,6 +72,20 @@ const MOCK_COMPANIES = [
|
|||||||
// 알림 폴링 간격 (30초)
|
// 알림 폴링 간격 (30초)
|
||||||
const NOTIFICATION_POLLING_INTERVAL = 30000;
|
const NOTIFICATION_POLLING_INTERVAL = 30000;
|
||||||
|
|
||||||
|
// 뱃지 색상 매핑 (TodayIssueSection과 동기화)
|
||||||
|
const BADGE_COLORS: Record<string, string> = {
|
||||||
|
'수주등록': 'bg-blue-100 text-blue-700',
|
||||||
|
'추심이슈': 'bg-purple-100 text-purple-700',
|
||||||
|
'안전재고': 'bg-orange-100 text-orange-700',
|
||||||
|
'지출승인': 'bg-green-100 text-green-700',
|
||||||
|
'세금신고': 'bg-red-100 text-red-700',
|
||||||
|
'결재요청': 'bg-yellow-100 text-yellow-700',
|
||||||
|
'신규업체': 'bg-emerald-100 text-emerald-700',
|
||||||
|
'입금': 'bg-cyan-100 text-cyan-700',
|
||||||
|
'출금': 'bg-pink-100 text-pink-700',
|
||||||
|
'기타': 'bg-gray-100 text-gray-700',
|
||||||
|
};
|
||||||
|
|
||||||
interface AuthenticatedLayoutProps {
|
interface AuthenticatedLayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
@@ -774,9 +788,7 @@ export default function AuthenticatedLayout({ children }: AuthenticatedLayoutPro
|
|||||||
{/* 배지 */}
|
{/* 배지 */}
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<span className={`inline-flex items-center justify-center px-2 py-1 text-xs font-medium rounded-md ${
|
<span className={`inline-flex items-center justify-center px-2 py-1 text-xs font-medium rounded-md ${
|
||||||
notification.needs_approval
|
BADGE_COLORS[notification.badge] || BADGE_COLORS['기타']
|
||||||
? 'bg-red-100 text-red-700'
|
|
||||||
: 'bg-blue-100 text-blue-700'
|
|
||||||
}`}>
|
}`}>
|
||||||
{notification.badge}
|
{notification.badge}
|
||||||
</span>
|
</span>
|
||||||
@@ -1038,9 +1050,7 @@ export default function AuthenticatedLayout({ children }: AuthenticatedLayoutPro
|
|||||||
{/* 배지 */}
|
{/* 배지 */}
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<span className={`inline-flex items-center justify-center px-2 py-1 text-xs font-medium rounded-md ${
|
<span className={`inline-flex items-center justify-center px-2 py-1 text-xs font-medium rounded-md ${
|
||||||
notification.needs_approval
|
BADGE_COLORS[notification.badge] || BADGE_COLORS['기타']
|
||||||
? 'bg-red-100 text-red-700'
|
|
||||||
: 'bg-blue-100 text-blue-700'
|
|
||||||
}`}>
|
}`}>
|
||||||
{notification.badge}
|
{notification.badge}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -134,6 +134,20 @@ export async function getDeliveryMethodOptions() {
|
|||||||
return getCommonCodeOptions('delivery_method');
|
return getCommonCodeOptions('delivery_method');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 운임비용 코드 조회
|
||||||
|
*/
|
||||||
|
export async function getShippingCostCodes() {
|
||||||
|
return getCommonCodes('shipping_cost');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 운임비용 옵션 조회
|
||||||
|
*/
|
||||||
|
export async function getShippingCostOptions() {
|
||||||
|
return getCommonCodeOptions('shipping_cost');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 코드값으로 라벨 조회 (code → name 매핑)
|
* 코드값으로 라벨 조회 (code → name 매핑)
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user