From 0ee49b77aed778b7981efd67a31e4ff14586c4c7 Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 12 Dec 2025 10:44:59 +0900 Subject: [PATCH 01/11] =?UTF-8?q?=EC=84=A4=EB=B9=84=20=ED=92=88=EB=AA=A9?= =?UTF-8?q?=20=ED=95=98=EB=82=98=EB=A7=8C=20=EC=B6=94=EA=B0=80=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/common/ScreenModal.tsx | 79 ++++++++++++++++------ frontend/lib/utils/buttonActions.ts | 79 ++++++++++++++++++---- 2 files changed, 124 insertions(+), 34 deletions(-) diff --git a/frontend/components/common/ScreenModal.tsx b/frontend/components/common/ScreenModal.tsx index 4da781e6..811249a7 100644 --- a/frontend/components/common/ScreenModal.tsx +++ b/frontend/components/common/ScreenModal.tsx @@ -1,13 +1,7 @@ "use client"; import React, { useState, useEffect, useRef } from "react"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogDescription, -} from "@/components/ui/dialog"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"; import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; import { InteractiveScreenViewerDynamic } from "@/components/screen/InteractiveScreenViewerDynamic"; @@ -183,15 +177,66 @@ export const ScreenModal: React.FC = ({ className }) => { setOriginalData(editData); // ๐Ÿ†• ์›๋ณธ ๋ฐ์ดํ„ฐ ์ €์žฅ (UPDATE ํŒ๋‹จ์šฉ) } else { // ๐Ÿ†• ์‹ ๊ทœ ๋“ฑ๋ก ๋ชจ๋“œ: ๋ถ„ํ•  ํŒจ๋„ ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ๋ฏธ๋ฆฌ ์„ค์ • - // 1์ˆœ์œ„: ์ด๋ฒคํŠธ๋กœ ์ „๋‹ฌ๋œ splitPanelParentData (ํƒญ ์•ˆ์—์„œ ์—ด๋ฆฐ ๋ชจ๋‹ฌ) - // 2์ˆœ์œ„: splitPanelContext์—์„œ ์ง์ ‘ ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ (๋ถ„ํ•  ํŒจ๋„ ๋‚ด์—์„œ ์—ด๋ฆฐ ๋ชจ๋‹ฌ) - const parentData = + // ๐Ÿ”ง ์ค‘์š”: ์‹ ๊ทœ ๋“ฑ๋ก ์‹œ์—๋Š” ์—ฐ๊ฒฐ ํ•„๋“œ(equipment_code ๋“ฑ)๋งŒ ์ „๋‹ฌํ•ด์•ผ ํ•จ + // ๋ชจ๋“  ํ•„๋“œ๋ฅผ ์ „๋‹ฌํ•˜๋ฉด ๋™์ผํ•œ ์ปฌ๋Ÿผ๋ช…์ด ์žˆ์„ ๋•Œ ๋ถ€๋ชจ ๊ฐ’์ด ๋“ค์–ด๊ฐ€๋Š” ๋ฌธ์ œ ๋ฐœ์ƒ + // ์˜ˆ: ์„ค๋น„์˜ manufacturer๊ฐ€ ์†Œ๋ชจํ’ˆ์˜ manufacturer๋กœ ๋“ค์–ด๊ฐ + + // parentDataMapping์—์„œ ๋ช…์‹œ๋œ ํ•„๋“œ๋งŒ ์ถ”์ถœ + const parentDataMapping = splitPanelContext?.parentDataMapping || []; + + // ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ ์†Œ์Šค + const rawParentData = splitPanelParentData && Object.keys(splitPanelParentData).length > 0 ? splitPanelParentData - : splitPanelContext?.getMappedParentData() || {}; + : splitPanelContext?.selectedLeftData || {}; + + // ๐Ÿ”ง ์‹ ๊ทœ ๋“ฑ๋ก ๋ชจ๋“œ์—์„œ๋Š” ์—ฐ๊ฒฐ์— ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์ „๋‹ฌ + const parentData: Record = {}; + + // ํ•„์ˆ˜ ์—ฐ๊ฒฐ ํ•„๋“œ: company_code (๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ) + if (rawParentData.company_code) { + parentData.company_code = rawParentData.company_code; + } + + // parentDataMapping์— ์ •์˜๋œ ํ•„๋“œ๋งŒ ์ „๋‹ฌ + for (const mapping of parentDataMapping) { + const sourceValue = rawParentData[mapping.sourceColumn]; + if (sourceValue !== undefined && sourceValue !== null) { + parentData[mapping.targetColumn] = sourceValue; + console.log( + `๐Ÿ”— [ScreenModal] ๋งคํ•‘ ํ•„๋“œ ์ „๋‹ฌ: ${mapping.sourceColumn} โ†’ ${mapping.targetColumn} = ${sourceValue}`, + ); + } + } + + // parentDataMapping์ด ๋น„์–ด์žˆ์œผ๋ฉด ์—ฐ๊ฒฐ ํ•„๋“œ ์ž๋™ ๊ฐ์ง€ (equipment_code, xxx_code, xxx_id ํŒจํ„ด) + if (parentDataMapping.length === 0) { + const linkFieldPatterns = ["_code", "_id"]; + const excludeFields = [ + "id", + "company_code", + "created_date", + "updated_date", + "created_at", + "updated_at", + "writer", + ]; + + for (const [key, value] of Object.entries(rawParentData)) { + if (excludeFields.includes(key)) continue; + if (value === undefined || value === null) continue; + + // ์—ฐ๊ฒฐ ํ•„๋“œ ํŒจํ„ด ํ™•์ธ + const isLinkField = linkFieldPatterns.some((pattern) => key.endsWith(pattern)); + if (isLinkField) { + parentData[key] = value; + console.log(`๐Ÿ”— [ScreenModal] ์—ฐ๊ฒฐ ํ•„๋“œ ์ž๋™ ๊ฐ์ง€: ${key} = ${value}`); + } + } + } if (Object.keys(parentData).length > 0) { - console.log("๐Ÿ”— [ScreenModal] ๋ถ„ํ•  ํŒจ๋„ ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐ๊ฐ’ ์„ค์ •:", parentData); + console.log("๐Ÿ”— [ScreenModal] ๋ถ„ํ•  ํŒจ๋„ ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐ๊ฐ’ ์„ค์ • (์—ฐ๊ฒฐ ํ•„๋“œ๋งŒ):", parentData); setFormData(parentData); } else { setFormData({}); @@ -604,19 +649,15 @@ export const ScreenModal: React.FC = ({ className }) => {
{modalState.title} {modalState.description && !loading && ( - - {modalState.description} - + {modalState.description} )} {loading && ( - - {loading ? "ํ™”๋ฉด์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค..." : ""} - + {loading ? "ํ™”๋ฉด์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค..." : ""} )}
-
+
{loading ? (
diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 1ced2836..e7da5833 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -681,13 +681,52 @@ export class ButtonActionExecutor { console.log("๐Ÿ“ฆ ์ตœ์ข… formData:", JSON.stringify(formData, null, 2)); // ๐Ÿ†• ๋ถ„ํ•  ํŒจ๋„ ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ ๋ณ‘ํ•ฉ (์ขŒ์ธก ํ™”๋ฉด์—์„œ ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ) - const splitPanelData = context.splitPanelParentData || {}; - if (Object.keys(splitPanelData).length > 0) { - console.log("๐Ÿ”— [handleSave] ๋ถ„ํ•  ํŒจ๋„ ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ ๋ณ‘ํ•ฉ:", splitPanelData); + // ๐Ÿ”ง ์ค‘์š”: ์‹ ๊ทœ ๋“ฑ๋ก ์‹œ์—๋Š” ์—ฐ๊ฒฐ ํ•„๋“œ(equipment_code ๋“ฑ)๋งŒ ๋ณ‘ํ•ฉํ•ด์•ผ ํ•จ + // ๋ชจ๋“  ํ•„๋“œ๋ฅผ ๋ณ‘ํ•ฉํ•˜๋ฉด ๋™์ผํ•œ ์ปฌ๋Ÿผ๋ช…์ด ์žˆ์„ ๋•Œ ๋ถ€๋ชจ ๊ฐ’์ด ๋“ค์–ด๊ฐ€๋Š” ๋ฌธ์ œ ๋ฐœ์ƒ + // ์˜ˆ: ์„ค๋น„์˜ manufacturer๊ฐ€ ์†Œ๋ชจํ’ˆ์˜ manufacturer๋กœ ๋“ค์–ด๊ฐ + const rawSplitPanelData = context.splitPanelParentData || {}; + + // INSERT ๋ชจ๋“œ์—์„œ๋Š” ์—ฐ๊ฒฐ์— ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์ถ”์ถœ + const cleanedSplitPanelData: Record = {}; + + // ํ•„์ˆ˜ ์—ฐ๊ฒฐ ํ•„๋“œ: company_code (๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ) + if (rawSplitPanelData.company_code) { + cleanedSplitPanelData.company_code = rawSplitPanelData.company_code; + } + + // ์—ฐ๊ฒฐ ํ•„๋“œ ํŒจํ„ด์œผ๋กœ ์ž๋™ ๊ฐ์ง€ (equipment_code, xxx_code, xxx_id ํŒจํ„ด) + const linkFieldPatterns = ["_code", "_id"]; + const excludeFields = [ + "id", + "company_code", + "created_date", + "updated_date", + "created_at", + "updated_at", + "writer", + "created_by", + "updated_by", + ]; + + for (const [key, value] of Object.entries(rawSplitPanelData)) { + if (excludeFields.includes(key)) continue; + if (value === undefined || value === null) continue; + + // ์—ฐ๊ฒฐ ํ•„๋“œ ํŒจํ„ด ํ™•์ธ + const isLinkField = linkFieldPatterns.some((pattern) => key.endsWith(pattern)); + if (isLinkField) { + cleanedSplitPanelData[key] = value; + console.log(`๐Ÿ”— [handleSave] INSERT ๋ชจ๋“œ - ์—ฐ๊ฒฐ ํ•„๋“œ๋งŒ ๋ณ‘ํ•ฉ: ${key} = ${value}`); + } + } + + if (Object.keys(rawSplitPanelData).length > 0) { + console.log("๐Ÿงน [handleSave] ์›๋ณธ ๋ถ„ํ•  ํŒจ๋„ ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ:", Object.keys(rawSplitPanelData)); + console.log("๐Ÿงน [handleSave] ์ •๋ฆฌ๋œ ๋ถ„ํ•  ํŒจ๋„ ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ (์—ฐ๊ฒฐ ํ•„๋“œ๋งŒ):", cleanedSplitPanelData); } const dataWithUserInfo = { - ...splitPanelData, // ๋ถ„ํ•  ํŒจ๋„ ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ ๋จผ์ € ์ ์šฉ + ...cleanedSplitPanelData, // ์ •๋ฆฌ๋œ ๋ถ„ํ•  ํŒจ๋„ ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ ๋จผ์ € ์ ์šฉ ...formData, // ํผ ๋ฐ์ดํ„ฐ๊ฐ€ ์šฐ์„  (๋ฎ์–ด์“ฐ๊ธฐ ๊ฐ€๋Šฅ) writer: formData.writer || writerValue, // โœ… ์ž…๋ ฅ๊ฐ’ ์šฐ์„ , ์—†์œผ๋ฉด userId created_by: writerValue, // created_by๋Š” ํ•ญ์ƒ ๋กœ๊ทธ์ธํ•œ ์‚ฌ๋žŒ @@ -695,6 +734,12 @@ export class ButtonActionExecutor { company_code: formData.company_code || companyCodeValue, // โœ… ์ž…๋ ฅ๊ฐ’ ์šฐ์„ , ์—†์œผ๋ฉด user.companyCode }; + // ๐Ÿ”ง formData์—์„œ๋„ id ์ œ๊ฑฐ (์‹ ๊ทœ INSERT์ด๋ฏ€๋กœ) + if ("id" in dataWithUserInfo && !formData.id) { + console.log("๐Ÿ—‘๏ธ [handleSave] INSERT ๋ชจ๋“œ - dataWithUserInfo์—์„œ id ์ œ๊ฑฐ:", dataWithUserInfo.id); + delete dataWithUserInfo.id; + } + // _numberingRuleId ํ•„๋“œ ์ œ๊ฑฐ (์‹ค์ œ ์ €์žฅํ•˜์ง€ ์•Š์Œ) for (const key of Object.keys(dataWithUserInfo)) { if (key.endsWith("_numberingRuleId")) { @@ -1578,14 +1623,16 @@ export class ButtonActionExecutor { /** * ๋ชจ๋‹ฌ ์•ก์…˜ ์ฒ˜๋ฆฌ + * ๐Ÿ”ง modal ์•ก์…˜์€ ํ•ญ์ƒ ์‹ ๊ทœ ๋“ฑ๋ก(INSERT) ๋ชจ๋“œ๋กœ ๋™์ž‘ + * edit ์•ก์…˜๋งŒ ์ˆ˜์ •(UPDATE) ๋ชจ๋“œ๋กœ ๋™์ž‘ํ•ด์•ผ ํ•จ */ private static async handleModal(config: ButtonActionConfig, context: ButtonActionContext): Promise { // ๋ชจ๋‹ฌ ์—ด๊ธฐ ๋กœ์ง - console.log("๋ชจ๋‹ฌ ์—ด๊ธฐ:", { + console.log("๋ชจ๋‹ฌ ์—ด๊ธฐ (์‹ ๊ทœ ๋“ฑ๋ก ๋ชจ๋“œ):", { title: config.modalTitle, size: config.modalSize, targetScreenId: config.targetScreenId, - selectedRowsData: context.selectedRowsData, + // ๐Ÿ”ง selectedRowsData๋Š” modal ์•ก์…˜์—์„œ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ (์‹ ๊ทœ ๋“ฑ๋ก์ด๋ฏ€๋กœ) }); if (config.targetScreenId) { @@ -1602,10 +1649,11 @@ export class ButtonActionExecutor { } } - // ๐Ÿ†• ์„ ํƒ๋œ ํ–‰ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ - const selectedData = context.selectedRowsData || []; - console.log("๐Ÿ“ฆ [handleModal] ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ:", selectedData); - console.log("๐Ÿ“ฆ [handleModal] ๋ถ„ํ•  ํŒจ๋„ ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ:", context.splitPanelParentData); + // ๐Ÿ”ง modal ์•ก์…˜์€ ์‹ ๊ทœ ๋“ฑ๋ก์ด๋ฏ€๋กœ selectedData๋ฅผ ์ „๋‹ฌํ•˜์ง€ ์•Š์Œ + // selectedData๊ฐ€ ์žˆ์œผ๋ฉด ScreenModal์—์„œ originalData๋กœ ์ธ์‹ํ•˜์—ฌ UPDATE ๋ชจ๋“œ๋กœ ๋™์ž‘ํ•˜๊ฒŒ ๋จ + // edit ์•ก์…˜๋งŒ selectedData/editData๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ UPDATE ๋ชจ๋“œ๋กœ ๋™์ž‘ + console.log("๐Ÿ“ฆ [handleModal] ์‹ ๊ทœ ๋“ฑ๋ก ๋ชจ๋“œ - selectedData ์ „๋‹ฌํ•˜์ง€ ์•Š์Œ"); + console.log("๐Ÿ“ฆ [handleModal] ๋ถ„ํ•  ํŒจ๋„ ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ (์ดˆ๊ธฐ๊ฐ’์œผ๋กœ ์‚ฌ์šฉ):", context.splitPanelParentData); // ์ „์—ญ ๋ชจ๋‹ฌ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•œ ์ด๋ฒคํŠธ ๋ฐœ์ƒ const modalEvent = new CustomEvent("openScreenModal", { @@ -1614,10 +1662,11 @@ export class ButtonActionExecutor { title: config.modalTitle || "ํ™”๋ฉด", description: description, size: config.modalSize || "md", - // ๐Ÿ†• ์„ ํƒ๋œ ํ–‰ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ - selectedData: selectedData, - selectedIds: selectedData.map((row: any) => row.id).filter(Boolean), - // ๐Ÿ†• ๋ถ„ํ•  ํŒจ๋„ ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ (ํƒญ ์•ˆ ๋ชจ๋‹ฌ์—์„œ ์‚ฌ์šฉ) + // ๐Ÿ”ง ์‹ ๊ทœ ๋“ฑ๋ก์ด๋ฏ€๋กœ selectedData/selectedIds๋ฅผ ์ „๋‹ฌํ•˜์ง€ ์•Š์Œ + // edit ์•ก์…˜์—์„œ๋งŒ ์ด ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉ + selectedData: [], + selectedIds: [], + // ๐Ÿ†• ๋ถ„ํ•  ํŒจ๋„ ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ (ํƒญ ์•ˆ ๋ชจ๋‹ฌ์—์„œ ์ดˆ๊ธฐ๊ฐ’์œผ๋กœ ์‚ฌ์šฉ) splitPanelParentData: context.splitPanelParentData || {}, }, }); @@ -2663,7 +2712,7 @@ export class ButtonActionExecutor { const { executeNodeFlow } = await import("@/lib/api/nodeFlows"); // ๋ฐ์ดํ„ฐ ์†Œ์Šค ์ค€๋น„ - let sourceData: any = context.formData || {}; + const sourceData: any = context.formData || {}; // repeat-screen-modal ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ๋ณ‘ํ•ฉ const repeatScreenModalKeys = Object.keys(context.formData || {}).filter((key) => From a9135165d9941a4390c690822d2b2e643a3d507b Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Fri, 12 Dec 2025 10:55:09 +0900 Subject: [PATCH 02/11] =?UTF-8?q?fix:=20UniversalFormModal=20=EC=B1=84?= =?UTF-8?q?=EB=B2=88=20=EA=B7=9C=EC=B9=99=20=EC=9E=90=EB=8F=99=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ๋ชจ๋‹ฌ ์žฌ์˜คํ”ˆ ์‹œ ๋™์ผ ๋ฒˆํ˜ธ ์œ ์ง€ (previewCode ์‚ฌ์šฉ) - ์ €์žฅ ์‹œ ์ •์ƒ์ ์ธ ์ˆœ๋ฒˆ ์ฆ๊ฐ€ (allocateCode์—์„œ nextSequence ์‚ฌ์šฉ) - refreshKey๋ฅผ React key๋กœ ์ „๋‹ฌํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ ๊ฐ•์ œ ๋ฆฌ๋งˆ์šดํŠธ - ruleId๋ฅผ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊นŒ์ง€ ์ „๋‹ฌํ•˜์—ฌ buttonActions์—์„œ ๊ฐ์ง€ - ๋ฏธ๋ฆฌ๋ณด๊ธฐ์™€ ์ €์žฅ ๋ฒˆํ˜ธ ์ผ์น˜ (currentSequence + 1 ํ†ต์ผ) --- .../src/services/numberingRuleService.ts | 5 ++- .../lib/registry/DynamicComponentRenderer.tsx | 3 +- .../UniversalFormModalComponent.tsx | 42 +++++++++++++------ 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/backend-node/src/services/numberingRuleService.ts b/backend-node/src/services/numberingRuleService.ts index 4a9b53a4..8208ecc5 100644 --- a/backend-node/src/services/numberingRuleService.ts +++ b/backend-node/src/services/numberingRuleService.ts @@ -959,9 +959,10 @@ class NumberingRuleService { switch (part.partType) { case "sequence": { - // ์ˆœ๋ฒˆ (์ž๋™ ์ฆ๊ฐ€ ์ˆซ์ž) + // ์ˆœ๋ฒˆ (์ž๋™ ์ฆ๊ฐ€ ์ˆซ์ž - ๋‹ค์Œ ๋ฒˆํ˜ธ ์‚ฌ์šฉ) const length = autoConfig.sequenceLength || 3; - return String(rule.currentSequence || 1).padStart(length, "0"); + const nextSequence = (rule.currentSequence || 0) + 1; + return String(nextSequence).padStart(length, "0"); } case "number": { diff --git a/frontend/lib/registry/DynamicComponentRenderer.tsx b/frontend/lib/registry/DynamicComponentRenderer.tsx index 4d12309b..dc92c38a 100644 --- a/frontend/lib/registry/DynamicComponentRenderer.tsx +++ b/frontend/lib/registry/DynamicComponentRenderer.tsx @@ -468,7 +468,8 @@ export const DynamicComponentRenderer: React.FC = return rendererInstance.render(); } else { // ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ - return ; + // refreshKey๋ฅผ React key๋กœ ์ „๋‹ฌํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ ๋ฆฌ๋งˆ์šดํŠธ ๊ฐ•์ œ + return ; } } } catch (error) { diff --git a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx index a78d2e95..14f84858 100644 --- a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx +++ b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx @@ -23,7 +23,7 @@ import { ChevronDown, ChevronUp, Plus, Trash2, RefreshCw, Loader2 } from "lucide import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; -import { generateNumberingCode, allocateNumberingCode } from "@/lib/api/numberingRule"; +import { generateNumberingCode, allocateNumberingCode, previewNumberingCode } from "@/lib/api/numberingRule"; import { useCascadingDropdown } from "@/hooks/useCascadingDropdown"; import { CascadingDropdownConfig } from "@/types/screen-management"; @@ -257,8 +257,11 @@ export function UniversalFormModalComponent({ // ์™ธ๋ถ€ formData์— ์ด๋ฏธ ๊ฐ’์ด ์žˆ์–ด๋„ UniversalFormModal ๊ฐ’์œผ๋กœ ๋ฎ์–ด์”€ // (UniversalFormModal์ด ํ•ด๋‹น ํ•„๋“œ์˜ ์ฃผ์ธ์ด๋ฏ€๋กœ) for (const [key, value] of Object.entries(formData)) { - // ์„ค์ •์— ์ •์˜๋œ ํ•„๋“œ๋งŒ ๋ณ‘ํ•ฉ - if (configuredFields.has(key)) { + // ์„ค์ •์— ์ •์˜๋œ ํ•„๋“œ ๋˜๋Š” ์ฑ„๋ฒˆ ๊ทœ์น™ ID ํ•„๋“œ๋งŒ ๋ณ‘ํ•ฉ + const isConfiguredField = configuredFields.has(key); + const isNumberingRuleId = key.endsWith("_numberingRuleId"); + + if (isConfiguredField || isNumberingRuleId) { if (value !== undefined && value !== null && value !== "") { event.detail.formData[key] = value; console.log(`[UniversalFormModal] ํ•„๋“œ ๋ณ‘ํ•ฉ: ${key} =`, value); @@ -401,7 +404,7 @@ export function UniversalFormModalComponent({ } isGeneratingRef.current = true; // ์ง„ํ–‰ ์ค‘ ํ‘œ์‹œ - console.log('[์ฑ„๋ฒˆ] ์ƒ์„ฑ ์‹œ์ž‘'); + console.log('[์ฑ„๋ฒˆ] ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ƒ์„ฑ ์‹œ์ž‘'); const updatedData = { ...currentFormData }; let hasChanges = false; @@ -417,17 +420,32 @@ export function UniversalFormModalComponent({ !updatedData[field.columnName] ) { try { - console.log(`[์ฑ„๋ฒˆ API ํ˜ธ์ถœ] ${field.columnName}, ruleId: ${field.numberingRule.ruleId}`); - // generateOnOpen: ๋ชจ๋‹ฌ ์—ด ๋•Œ ์‹ค์ œ ์ˆœ๋ฒˆ ํ• ๋‹น (DB ์‹œํ€€์Šค ์ฆ‰์‹œ ์ฆ๊ฐ€) - const response = await allocateNumberingCode(field.numberingRule.ruleId); + console.log(`[์ฑ„๋ฒˆ ๋ฏธ๋ฆฌ๋ณด๊ธฐ API ํ˜ธ์ถœ] ${field.columnName}, ruleId: ${field.numberingRule.ruleId}`); + // generateOnOpen: ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋งŒ ํ‘œ์‹œ (DB ์‹œํ€€์Šค ์ฆ๊ฐ€ ์•ˆ ํ•จ) + const response = await previewNumberingCode(field.numberingRule.ruleId); if (response.success && response.data?.generatedCode) { updatedData[field.columnName] = response.data.generatedCode; + + // ์ €์žฅ ์‹œ ์‹ค์ œ ํ• ๋‹น์„ ์œ„ํ•ด ruleId ์ €์žฅ (TextInput๊ณผ ๋™์ผํ•œ ํ‚ค ํ˜•์‹) + const ruleIdKey = `${field.columnName}_numberingRuleId`; + updatedData[ruleIdKey] = field.numberingRule.ruleId; + hasChanges = true; numberingGeneratedRef.current = true; // ์ƒ์„ฑ ์™„๋ฃŒ ํ‘œ์‹œ - console.log(`[์ฑ„๋ฒˆ ์™„๋ฃŒ] ${field.columnName} = ${response.data.generatedCode}`); + console.log(`[์ฑ„๋ฒˆ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์™„๋ฃŒ] ${field.columnName} = ${response.data.generatedCode} (์ €์žฅ ์‹œ ์‹ค์ œ ํ• ๋‹น)`); + console.log(`[์ฑ„๋ฒˆ ๊ทœ์น™ ID ์ €์žฅ] ${ruleIdKey} = ${field.numberingRule.ruleId}`); + + // ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—๋„ ruleId ์ „๋‹ฌ (ModalRepeaterTable โ†’ ScreenModal) + if (onChange) { + onChange({ + ...updatedData, + [ruleIdKey]: field.numberingRule.ruleId + }); + console.log(`[์ฑ„๋ฒˆ] ๋ถ€๋ชจ์—๊ฒŒ ruleId ์ „๋‹ฌ: ${ruleIdKey}`); + } } } catch (error) { - console.error(`์ฑ„๋ฒˆ๊ทœ์น™ ์ƒ์„ฑ ์‹คํŒจ (${field.columnName}):`, error); + console.error(`์ฑ„๋ฒˆ๊ทœ์น™ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์‹คํŒจ (${field.columnName}):`, error); } } } @@ -439,7 +457,7 @@ export function UniversalFormModalComponent({ setFormData(updatedData); } }, - [config], + [config, onChange], ); // ํ•„๋“œ ๊ฐ’ ๋ณ€๊ฒฝ ํ•ธ๋“ค๋Ÿฌ @@ -659,9 +677,9 @@ export function UniversalFormModalComponent({ const saveSingleRow = useCallback(async () => { const dataToSave = { ...formData }; - // ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํ•„๋“œ ์ œ๊ฑฐ + // ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํ•„๋“œ ์ œ๊ฑฐ (์ฑ„๋ฒˆ ๊ทœ์น™ ID๋Š” ์œ ์ง€ - buttonActions.ts์—์„œ ์‚ฌ์šฉ) Object.keys(dataToSave).forEach((key) => { - if (key.startsWith("_")) { + if (key.startsWith("_") && !key.includes("_numberingRuleId")) { delete dataToSave[key]; } }); From 1680163c612c6a4e13375ec61b4d19be49c8e834 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Fri, 12 Dec 2025 11:10:51 +0900 Subject: [PATCH 03/11] =?UTF-8?q?fix:=20ModalRepeaterTable=20=EB=B9=88=20?= =?UTF-8?q?=ED=96=89=20=EC=9E=90=EB=8F=99=20=ED=91=9C=EC=8B=9C=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ์‹ ๊ทœ ๋“ฑ๋ก ๋ชจ๋‹ฌ ์˜คํ”ˆ ์‹œ ๋นˆ ๊ฐ์ฒด ํ•„ํ„ฐ๋ง ๊ธฐ๋Šฅ ์ถ”๊ฐ€ - isEmptyRow ํ•จ์ˆ˜๋กœ ์•ˆ์ „ํ•œ ๋นˆ ๊ฐ์ฒด ํŒ๋‹จ (id ํ•„๋“œ ์ฒดํฌ) - useState ์ดˆ๊ธฐํ™” ๋ฐ useEffect ๋™๊ธฐํ™”์— ํ•„ํ„ฐ๋ง ์ ์šฉ - ์ˆ˜์ • ๋ชจ๋‹ฌ์˜ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋Š” id ํ•„๋“œ๋กœ ๋ณดํ˜ธ --- .../ModalRepeaterTableComponent.tsx | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx b/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx index 92eb4bb7..64c9e95f 100644 --- a/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx +++ b/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx @@ -198,14 +198,43 @@ export function ModalRepeaterTableComponent({ const columnName = component?.columnName; const externalValue = (columnName && formData?.[columnName]) || componentConfig?.value || propValue || []; + // ๋นˆ ๊ฐ์ฒด ํŒ๋‹จ ํ•จ์ˆ˜ (์ˆ˜์ • ๋ชจ๋‹ฌ์˜ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋Š” ์œ ์ง€) + const isEmptyRow = (item: any): boolean => { + if (!item || typeof item !== 'object') return true; + + // id๊ฐ€ ์žˆ์œผ๋ฉด ์‹ค์ œ ๋ฐ์ดํ„ฐ (์ˆ˜์ • ๋ชจ๋‹ฌ) + if (item.id) return false; + + // ๋ชจ๋“  ๊ฐ’์ด ๋น„์–ด์žˆ๋Š”์ง€ ํ™•์ธ (๊ณ„์‚ฐ ํ•„๋“œ ์ œ์™ธ) + const hasValue = Object.entries(item).some(([key, value]) => { + // ๊ณ„์‚ฐ ํ•„๋“œ๋‚˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋Š” ์ œ์™ธ + if (key.startsWith('_') || key === 'total_amount') return false; + + // ์‹ค์ œ ๊ฐ’์ด ์žˆ๋Š”์ง€ ํ™•์ธ + return value !== undefined && + value !== null && + value !== '' && + value !== 0 && + value !== '0' && + value !== '0.00'; + }); + + return !hasValue; + }; + // ๐Ÿ†• ๋‚ด๋ถ€ ์ƒํƒœ๋กœ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ (์ฆ‰์‹œ UI ๋ฐ˜์˜์„ ์œ„ํ•ด) - const [localValue, setLocalValue] = useState(externalValue); + const [localValue, setLocalValue] = useState(() => { + return externalValue.filter((item) => !isEmptyRow(item)); + }); // ๐Ÿ†• ์™ธ๋ถ€ ๊ฐ’(formData, propValue) ๋ณ€๊ฒฝ ์‹œ ๋‚ด๋ถ€ ์ƒํƒœ ๋™๊ธฐํ™” useEffect(() => { + // ๋นˆ ๊ฐ์ฒด ํ•„ํ„ฐ๋ง + const filteredValue = externalValue.filter((item) => !isEmptyRow(item)); + // ์™ธ๋ถ€ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜์—ˆ๊ณ , ๋‚ด๋ถ€ ๊ฐ’๊ณผ ๋‹ค๋ฅธ ๊ฒฝ์šฐ์—๋งŒ ๋™๊ธฐํ™” - if (JSON.stringify(externalValue) !== JSON.stringify(localValue)) { - setLocalValue(externalValue); + if (JSON.stringify(filteredValue) !== JSON.stringify(localValue)) { + setLocalValue(filteredValue); } }, [externalValue]); From c85841b59fe65ed00ca01775c20fd71ccd9531a1 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Fri, 12 Dec 2025 13:46:20 +0900 Subject: [PATCH 04/11] =?UTF-8?q?fix(repeat-screen-modal):=20=EC=99=B8?= =?UTF-8?q?=EB=B6=80=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=A1=B0=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=20ID=20=ED=83=80=EC=9E=85=20=EB=B3=80=ED=99=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ์กฐ์ธ ํ‚ค๊ฐ€ '_id' ๋˜๋Š” 'id'์ธ ๊ฒฝ์šฐ ๋ฌธ์ž์—ด์„ ์ˆซ์ž๋กœ ๋ณ€ํ™˜ - ๋ฐฑ์—”๋“œ ILIKE ๊ฒ€์ƒ‰ ๋ฐฉ์ง€๋กœ ์ •ํ™•ํ•œ ID ๋งค์นญ ๋ณด์žฅ - API ํ˜ธ์ถœ ํŒŒ๋ผ๋ฏธํ„ฐ ๋กœ๊น… ์ถ”๊ฐ€ (๋””๋ฒ„๊น…์šฉ) --- .../RepeatScreenModalComponent.tsx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx b/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx index 980bbfe9..d5490467 100644 --- a/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx +++ b/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx @@ -464,8 +464,17 @@ export function RepeatScreenModalComponent({ // ์กฐ์ธ ์กฐ๊ฑด ์ƒ์„ฑ const filters: Record = {}; for (const condition of dataSourceConfig.joinConditions) { - const refValue = representativeData[condition.referenceKey]; + let refValue = representativeData[condition.referenceKey]; if (refValue !== undefined && refValue !== null) { + // ์ˆซ์žํ˜• ID์ธ ๊ฒฝ์šฐ ์ˆซ์ž๋กœ ๋ณ€ํ™˜ (๋ฌธ์ž์—ด '189' โ†’ ์ˆซ์ž 189) + // ๋ฐฑ์—”๋“œ์—์„œ entity ํƒ€์ž… ์ปฌ๋Ÿผ ๊ฒ€์ƒ‰ ์‹œ ๋ฌธ์ž์—ด์ด๋ฉด ILIKE ๊ฒ€์ƒ‰์„ ์ˆ˜ํ–‰ํ•˜๋ฏ€๋กœ + // ์ •ํ™•ํ•œ ID ๋งค์นญ์„ ์œ„ํ•ด ์ˆซ์ž๋กœ ๋ณ€ํ™˜ํ•ด์•ผ ํ•จ + if (condition.sourceKey.endsWith('_id') || condition.sourceKey === 'id') { + const numValue = Number(refValue); + if (!isNaN(numValue)) { + refValue = numValue; + } + } filters[condition.sourceKey] = refValue; } } @@ -475,6 +484,14 @@ export function RepeatScreenModalComponent({ continue; } + console.log(`[RepeatScreenModal] ์™ธ๋ถ€ ํ…Œ์ด๋ธ” API ํ˜ธ์ถœ:`, { + sourceTable: dataSourceConfig.sourceTable, + filters, + joinConditions: dataSourceConfig.joinConditions, + representativeDataId: representativeData.id, + representativeDataIdType: typeof representativeData.id, + }); + // API ํ˜ธ์ถœ - ๋ฉ”์ธ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ const response = await apiClient.post( `/table-management/tables/${dataSourceConfig.sourceTable}/data`, From 3a6af2fb71c971bab9b275c39326efb8fbcb6ba5 Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 12 Dec 2025 13:50:33 +0900 Subject: [PATCH 05/11] =?UTF-8?q?=EB=B6=84=ED=95=A0=ED=8C=A8=EB=84=90=20?= =?UTF-8?q?=EC=A1=B0=EC=9D=B8=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SplitPanelLayoutComponent.tsx | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx index 9d1d0811..ac5cc8d1 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx @@ -100,6 +100,13 @@ export const SplitPanelLayoutComponent: React.FC return item[exactKey]; } + // ๐Ÿ†• 2-1๏ธโƒฃ item_id ํŒจํ„ด ์‹œ๋„ (๋ฐฑ์—”๋“œ๊ฐ€ item_id_xxx ํ˜•์‹์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ) + // ์˜ˆ: item_info.item_name โ†’ item_id_item_name + const idPatternKey = `${tableName.replace("_info", "_id").replace("_mng", "_id")}_${fieldName}`; + if (item[idPatternKey] !== undefined) { + return item[idPatternKey]; + } + // 3๏ธโƒฃ ๋ณ„์นญ ํŒจํ„ด: ์†Œ์Šค์ปฌ๋Ÿผ_name (๊ธฐ๋ณธ ํ‘œ์‹œ ์ปฌ๋Ÿผ์šฉ) // ์˜ˆ: item_code_name (item_name์˜ ๋ณ„์นญ) if (fieldName === "item_name" || fieldName === "name") { @@ -107,6 +114,11 @@ export const SplitPanelLayoutComponent: React.FC if (item[aliasKey] !== undefined) { return item[aliasKey]; } + // ๐Ÿ†• item_id_name ํŒจํ„ด๋„ ์‹œ๋„ + const idAliasKey = `${tableName.replace("_info", "_id").replace("_mng", "_id")}_name`; + if (item[idAliasKey] !== undefined) { + return item[idAliasKey]; + } } // 4๏ธโƒฃ entityColumnMap์—์„œ ๋งคํ•‘ ์ฐพ๊ธฐ (ํ™”๋ฉด ์„ค์ •์—์„œ ์ง€์ •๋œ ๊ฒฝ์šฐ) @@ -1023,7 +1035,7 @@ export const SplitPanelLayoutComponent: React.FC const uniqueValues = new Set(); leftData.forEach((item) => { - // ๐Ÿ†• ์กฐ์ธ ์ปฌ๋Ÿผ ์ฒ˜๋ฆฌ (item_info.standard โ†’ item_code_standard) + // ๐Ÿ†• ์กฐ์ธ ์ปฌ๋Ÿผ ์ฒ˜๋ฆฌ (item_info.standard โ†’ item_code_standard ๋˜๋Š” item_id_standard) let value: any; if (columnName.includes(".")) { @@ -1035,10 +1047,21 @@ export const SplitPanelLayoutComponent: React.FC const exactKey = `${inferredSourceColumn}_${fieldName}`; value = item[exactKey]; - // ๊ธฐ๋ณธ ๋ณ„์นญ ํŒจํ„ด ์‹œ๋„ (item_code_name) + // ๐Ÿ†• item_id ํŒจํ„ด ์‹œ๋„ + if (value === undefined) { + const idPatternKey = `${refTable.replace("_info", "_id").replace("_mng", "_id")}_${fieldName}`; + value = item[idPatternKey]; + } + + // ๊ธฐ๋ณธ ๋ณ„์นญ ํŒจํ„ด ์‹œ๋„ (item_code_name ๋˜๋Š” item_id_name) if (value === undefined && (fieldName === "item_name" || fieldName === "name")) { const aliasKey = `${inferredSourceColumn}_name`; value = item[aliasKey]; + // item_id_name ํŒจํ„ด๋„ ์‹œ๋„ + if (value === undefined) { + const idAliasKey = `${refTable.replace("_info", "_id").replace("_mng", "_id")}_name`; + value = item[idAliasKey]; + } } } else { // ์ผ๋ฐ˜ ์ปฌ๋Ÿผ From 11215e3316a1d4decfa4455bdffc3eb39611823b Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Fri, 12 Dec 2025 14:02:17 +0900 Subject: [PATCH 06/11] =?UTF-8?q?chore:=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EC=88=98=EC=A3=BC=20=EB=93=B1=EB=A1=9D=20=EB=AA=A8=EB=93=88(or?= =?UTF-8?q?derController)=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ๋ฐฑ์—”๋“œ: orderController.ts, orderRoutes.ts ์‚ญ์ œ - ํ”„๋ก ํŠธ์—”๋“œ: components/order/, order-registration-modal/ ์‚ญ์ œ - app.ts, index.ts, getComponentConfigPanel.tsx์—์„œ ์ฐธ์กฐ ์ œ๊ฑฐ - ํ˜„์žฌ sales_order_mng ๊ธฐ๋ฐ˜ ์ˆ˜์ฃผ ์‹œ์Šคํ…œ ์‚ฌ์šฉ์œผ๋กœ ๊ตฌ ๋ชจ๋“ˆ ๋ถˆํ•„์š” --- backend-node/src/app.ts | 2 - .../src/controllers/orderController.ts | 276 --------- backend-node/src/routes/orderRoutes.ts | 20 - .../components/order/OrderCustomerSearch.tsx | 49 -- .../order/OrderItemRepeaterTable.tsx | 135 ----- .../order/OrderRegistrationModal.tsx | 572 ------------------ frontend/components/order/README.md | 374 ------------ frontend/components/order/orderConstants.ts | 21 - frontend/lib/registry/components/index.ts | 1 - .../OrderRegistrationModalConfigPanel.tsx | 93 --- .../OrderRegistrationModalRenderer.tsx | 56 -- .../order-registration-modal/index.ts | 68 --- .../lib/utils/getComponentConfigPanel.tsx | 3 - 13 files changed, 1670 deletions(-) delete mode 100644 backend-node/src/controllers/orderController.ts delete mode 100644 backend-node/src/routes/orderRoutes.ts delete mode 100644 frontend/components/order/OrderCustomerSearch.tsx delete mode 100644 frontend/components/order/OrderItemRepeaterTable.tsx delete mode 100644 frontend/components/order/OrderRegistrationModal.tsx delete mode 100644 frontend/components/order/README.md delete mode 100644 frontend/components/order/orderConstants.ts delete mode 100644 frontend/lib/registry/components/order-registration-modal/OrderRegistrationModalConfigPanel.tsx delete mode 100644 frontend/lib/registry/components/order-registration-modal/OrderRegistrationModalRenderer.tsx delete mode 100644 frontend/lib/registry/components/order-registration-modal/index.ts diff --git a/backend-node/src/app.ts b/backend-node/src/app.ts index 5c2415ea..652677ca 100644 --- a/backend-node/src/app.ts +++ b/backend-node/src/app.ts @@ -71,7 +71,6 @@ import tableCategoryValueRoutes from "./routes/tableCategoryValueRoutes"; // ์นด import codeMergeRoutes from "./routes/codeMergeRoutes"; // ์ฝ”๋“œ ๋ณ‘ํ•ฉ import numberingRuleRoutes from "./routes/numberingRuleRoutes"; // ์ฑ„๋ฒˆ ๊ทœ์น™ ๊ด€๋ฆฌ import entitySearchRoutes from "./routes/entitySearchRoutes"; // ์—”ํ‹ฐํ‹ฐ ๊ฒ€์ƒ‰ -import orderRoutes from "./routes/orderRoutes"; // ์ˆ˜์ฃผ ๊ด€๋ฆฌ import screenEmbeddingRoutes from "./routes/screenEmbeddingRoutes"; // ํ™”๋ฉด ์ž„๋ฒ ๋”ฉ ๋ฐ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ import vehicleTripRoutes from "./routes/vehicleTripRoutes"; // ์ฐจ๋Ÿ‰ ์šดํ–‰ ์ด๋ ฅ ๊ด€๋ฆฌ import driverRoutes from "./routes/driverRoutes"; // ๊ณต์ฐจ์ค‘๊ณ„ ์šด์ „์ž ๊ด€๋ฆฌ @@ -249,7 +248,6 @@ app.use("/api/table-categories", tableCategoryValueRoutes); // ์นดํ…Œ๊ณ ๋ฆฌ ๊ฐ’ app.use("/api/code-merge", codeMergeRoutes); // ์ฝ”๋“œ ๋ณ‘ํ•ฉ app.use("/api/numbering-rules", numberingRuleRoutes); // ์ฑ„๋ฒˆ ๊ทœ์น™ ๊ด€๋ฆฌ app.use("/api/entity-search", entitySearchRoutes); // ์—”ํ‹ฐํ‹ฐ ๊ฒ€์ƒ‰ -app.use("/api/orders", orderRoutes); // ์ˆ˜์ฃผ ๊ด€๋ฆฌ app.use("/api/driver", driverRoutes); // ๊ณต์ฐจ์ค‘๊ณ„ ์šด์ „์ž ๊ด€๋ฆฌ app.use("/api/tax-invoice", taxInvoiceRoutes); // ์„ธ๊ธˆ๊ณ„์‚ฐ์„œ ๊ด€๋ฆฌ app.use("/api/cascading-relations", cascadingRelationRoutes); // ์—ฐ์‡„ ๋“œ๋กญ๋‹ค์šด ๊ด€๊ณ„ ๊ด€๋ฆฌ diff --git a/backend-node/src/controllers/orderController.ts b/backend-node/src/controllers/orderController.ts deleted file mode 100644 index 82043964..00000000 --- a/backend-node/src/controllers/orderController.ts +++ /dev/null @@ -1,276 +0,0 @@ -import { Response } from "express"; -import { AuthenticatedRequest } from "../types/auth"; -import { getPool } from "../database/db"; -import { logger } from "../utils/logger"; - -/** - * ์ˆ˜์ฃผ ๋ฒˆํ˜ธ ์ƒ์„ฑ ํ•จ์ˆ˜ - * ํ˜•์‹: ORD + YYMMDD + 4์ž๋ฆฌ ์‹œํ€€์Šค - * ์˜ˆ: ORD250114001 - */ -async function generateOrderNumber(companyCode: string): Promise { - const pool = getPool(); - const today = new Date(); - const year = today.getFullYear().toString().slice(2); // 25 - const month = String(today.getMonth() + 1).padStart(2, "0"); // 01 - const day = String(today.getDate()).padStart(2, "0"); // 14 - const dateStr = `${year}${month}${day}`; // 250114 - - // ๋‹น์ผ ์ˆ˜์ฃผ ์นด์šดํŠธ ์กฐํšŒ - const countQuery = ` - SELECT COUNT(*) as count - FROM order_mng_master - WHERE objid LIKE $1 - AND writer LIKE $2 - `; - - const pattern = `ORD${dateStr}%`; - const result = await pool.query(countQuery, [pattern, `%${companyCode}%`]); - const count = parseInt(result.rows[0]?.count || "0"); - const seq = count + 1; - - return `ORD${dateStr}${String(seq).padStart(4, "0")}`; // ORD250114001 -} - -/** - * ์ˆ˜์ฃผ ๋“ฑ๋ก API - * POST /api/orders - */ -export async function createOrder(req: AuthenticatedRequest, res: Response) { - const pool = getPool(); - - try { - const { - inputMode, // ์ž…๋ ฅ ๋ฐฉ์‹ - customerCode, // ๊ฑฐ๋ž˜์ฒ˜ ์ฝ”๋“œ - deliveryDate, // ๋‚ฉํ’ˆ์ผ - items, // ํ’ˆ๋ชฉ ๋ชฉ๋ก - memo, // ๋ฉ”๋ชจ - } = req.body; - - // ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ - const companyCode = req.user!.companyCode; - const userId = req.user!.userId; - - // ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ - if (!customerCode) { - return res.status(400).json({ - success: false, - message: "๊ฑฐ๋ž˜์ฒ˜ ์ฝ”๋“œ๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค", - }); - } - - if (!items || items.length === 0) { - return res.status(400).json({ - success: false, - message: "ํ’ˆ๋ชฉ์€ ์ตœ์†Œ 1๊ฐœ ์ด์ƒ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค", - }); - } - - // ์ˆ˜์ฃผ ๋ฒˆํ˜ธ ์ƒ์„ฑ - const orderNo = await generateOrderNumber(companyCode); - - // ์ „์ฒด ๊ธˆ์•ก ๊ณ„์‚ฐ - const totalAmount = items.reduce( - (sum: number, item: any) => sum + (item.amount || 0), - 0 - ); - - // ์ˆ˜์ฃผ ๋งˆ์Šคํ„ฐ ์ƒ์„ฑ - const masterQuery = ` - INSERT INTO order_mng_master ( - objid, - partner_objid, - final_delivery_date, - reason, - status, - reg_date, - writer - ) VALUES ($1, $2, $3, $4, $5, NOW(), $6) - RETURNING * - `; - - const masterResult = await pool.query(masterQuery, [ - orderNo, - customerCode, - deliveryDate || null, - memo || null, - "์ง„ํ–‰์ค‘", - `${userId}|${companyCode}`, - ]); - - const masterObjid = masterResult.rows[0].objid; - - // ์ˆ˜์ฃผ ์ƒ์„ธ (ํ’ˆ๋ชฉ) ์ƒ์„ฑ - for (let i = 0; i < items.length; i++) { - const item = items[i]; - const subObjid = `${orderNo}_${i + 1}`; - - const subQuery = ` - INSERT INTO order_mng_sub ( - objid, - order_mng_master_objid, - part_objid, - partner_objid, - partner_price, - partner_qty, - delivery_date, - status, - regdate, - writer - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW(), $9) - `; - - await pool.query(subQuery, [ - subObjid, - masterObjid, - item.item_code || item.id, // ํ’ˆ๋ชฉ ์ฝ”๋“œ - customerCode, - item.unit_price || 0, - item.quantity || 0, - item.delivery_date || deliveryDate || null, - "์ง„ํ–‰์ค‘", - `${userId}|${companyCode}`, - ]); - } - - logger.info("์ˆ˜์ฃผ ๋“ฑ๋ก ์„ฑ๊ณต", { - companyCode, - orderNo, - masterObjid, - itemCount: items.length, - totalAmount, - }); - - res.json({ - success: true, - data: { - orderNo, - masterObjid, - itemCount: items.length, - totalAmount, - }, - message: "์ˆ˜์ฃผ๊ฐ€ ๋“ฑ๋ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค", - }); - } catch (error: any) { - logger.error("์ˆ˜์ฃผ ๋“ฑ๋ก ์˜ค๋ฅ˜", { - error: error.message, - stack: error.stack, - }); - res.status(500).json({ - success: false, - message: error.message || "์ˆ˜์ฃผ ๋“ฑ๋ก ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค", - }); - } -} - -/** - * ์ˆ˜์ฃผ ๋ชฉ๋ก ์กฐํšŒ API (๋งˆ์Šคํ„ฐ + ํ’ˆ๋ชฉ JOIN) - * GET /api/orders - */ -export async function getOrders(req: AuthenticatedRequest, res: Response) { - const pool = getPool(); - - try { - const { page = "1", limit = "20", searchText = "" } = req.query; - const companyCode = req.user!.companyCode; - - const offset = (parseInt(page as string) - 1) * parseInt(limit as string); - - // WHERE ์กฐ๊ฑด - const whereConditions: string[] = []; - const params: any[] = []; - let paramIndex = 1; - - // ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ (writer ํ•„๋“œ์— company_code ํฌํ•จ) - if (companyCode !== "*") { - whereConditions.push(`m.writer LIKE $${paramIndex}`); - params.push(`%${companyCode}%`); - paramIndex++; - } - - // ๊ฒ€์ƒ‰ - if (searchText) { - whereConditions.push(`m.objid LIKE $${paramIndex}`); - params.push(`%${searchText}%`); - paramIndex++; - } - - const whereClause = - whereConditions.length > 0 - ? `WHERE ${whereConditions.join(" AND ")}` - : ""; - - // ์นด์šดํŠธ ์ฟผ๋ฆฌ (๊ณ ์œ ํ•œ ์ˆ˜์ฃผ ๊ฐœ์ˆ˜) - const countQuery = ` - SELECT COUNT(DISTINCT m.objid) as count - FROM order_mng_master m - ${whereClause} - `; - const countResult = await pool.query(countQuery, params); - const total = parseInt(countResult.rows[0]?.count || "0"); - - // ๋ฐ์ดํ„ฐ ์ฟผ๋ฆฌ (๋งˆ์Šคํ„ฐ + ํ’ˆ๋ชฉ JOIN) - const dataQuery = ` - SELECT - m.objid as order_no, - m.partner_objid, - m.final_delivery_date, - m.reason, - m.status, - m.reg_date, - m.writer, - COALESCE( - json_agg( - CASE WHEN s.objid IS NOT NULL THEN - json_build_object( - 'sub_objid', s.objid, - 'part_objid', s.part_objid, - 'partner_price', s.partner_price, - 'partner_qty', s.partner_qty, - 'delivery_date', s.delivery_date, - 'status', s.status, - 'regdate', s.regdate - ) - END - ORDER BY s.regdate - ) FILTER (WHERE s.objid IS NOT NULL), - '[]'::json - ) as items - FROM order_mng_master m - LEFT JOIN order_mng_sub s ON m.objid = s.order_mng_master_objid - ${whereClause} - GROUP BY m.objid, m.partner_objid, m.final_delivery_date, m.reason, m.status, m.reg_date, m.writer - ORDER BY m.reg_date DESC - LIMIT $${paramIndex} OFFSET $${paramIndex + 1} - `; - - params.push(parseInt(limit as string)); - params.push(offset); - - const dataResult = await pool.query(dataQuery, params); - - logger.info("์ˆ˜์ฃผ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต", { - companyCode, - total, - page: parseInt(page as string), - itemCount: dataResult.rows.length, - }); - - res.json({ - success: true, - data: dataResult.rows, - pagination: { - total, - page: parseInt(page as string), - limit: parseInt(limit as string), - }, - }); - } catch (error: any) { - logger.error("์ˆ˜์ฃผ ๋ชฉ๋ก ์กฐํšŒ ์˜ค๋ฅ˜", { error: error.message }); - res.status(500).json({ - success: false, - message: error.message, - }); - } -} diff --git a/backend-node/src/routes/orderRoutes.ts b/backend-node/src/routes/orderRoutes.ts deleted file mode 100644 index a59b5f43..00000000 --- a/backend-node/src/routes/orderRoutes.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Router } from "express"; -import { authenticateToken } from "../middleware/authMiddleware"; -import { createOrder, getOrders } from "../controllers/orderController"; - -const router = Router(); - -/** - * ์ˆ˜์ฃผ ๋“ฑ๋ก - * POST /api/orders - */ -router.post("/", authenticateToken, createOrder); - -/** - * ์ˆ˜์ฃผ ๋ชฉ๋ก ์กฐํšŒ - * GET /api/orders - */ -router.get("/", authenticateToken, getOrders); - -export default router; - diff --git a/frontend/components/order/OrderCustomerSearch.tsx b/frontend/components/order/OrderCustomerSearch.tsx deleted file mode 100644 index bcd351f9..00000000 --- a/frontend/components/order/OrderCustomerSearch.tsx +++ /dev/null @@ -1,49 +0,0 @@ -"use client"; - -import React from "react"; -import { AutocompleteSearchInputComponent } from "@/lib/registry/components/autocomplete-search-input"; - -/** - * ์ˆ˜์ฃผ ๋“ฑ๋ก ์ „์šฉ ๊ฑฐ๋ž˜์ฒ˜ ๊ฒ€์ƒ‰ ์ปดํฌ๋„ŒํŠธ - * - * ์ด ์ปดํฌ๋„ŒํŠธ๋Š” ์ˆ˜์ฃผ ๋“ฑ๋ก ํ™”๋ฉด ์ „์šฉ์ด๋ฉฐ, ์„ค์ •์ด ๊ณ ์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. - * ๋ฒ”์šฉ AutocompleteSearchInput๊ณผ ๋‹ฌ๋ฆฌ customer_mng ํ…Œ์ด๋ธ”๋งŒ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. - */ - -interface OrderCustomerSearchProps { - /** ํ˜„์žฌ ์„ ํƒ๋œ ๊ฑฐ๋ž˜์ฒ˜ ์ฝ”๋“œ */ - value: string; - /** ๊ฑฐ๋ž˜์ฒ˜ ์„ ํƒ ์‹œ ์ฝœ๋ฐฑ (๊ฑฐ๋ž˜์ฒ˜ ์ฝ”๋“œ, ์ „์ฒด ๋ฐ์ดํ„ฐ) */ - onChange: (customerCode: string | null, fullData?: any) => void; - /** ๋น„ํ™œ์„ฑํ™” ์—ฌ๋ถ€ */ - disabled?: boolean; -} - -export function OrderCustomerSearch({ - value, - onChange, - disabled = false, -}: OrderCustomerSearchProps) { - return ( - - ); -} - diff --git a/frontend/components/order/OrderItemRepeaterTable.tsx b/frontend/components/order/OrderItemRepeaterTable.tsx deleted file mode 100644 index dbfe5eee..00000000 --- a/frontend/components/order/OrderItemRepeaterTable.tsx +++ /dev/null @@ -1,135 +0,0 @@ -"use client"; - -import React from "react"; -import { ModalRepeaterTableComponent } from "@/lib/registry/components/modal-repeater-table"; -import type { - RepeaterColumnConfig, - CalculationRule, -} from "@/lib/registry/components/modal-repeater-table"; - -/** - * ์ˆ˜์ฃผ ๋“ฑ๋ก ์ „์šฉ ํ’ˆ๋ชฉ ๋ฐ˜๋ณต ํ…Œ์ด๋ธ” ์ปดํฌ๋„ŒํŠธ - * - * ์ด ์ปดํฌ๋„ŒํŠธ๋Š” ์ˆ˜์ฃผ ๋“ฑ๋ก ํ™”๋ฉด ์ „์šฉ์ด๋ฉฐ, ์„ค์ •์ด ๊ณ ์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. - * ๋ฒ”์šฉ ModalRepeaterTable๊ณผ ๋‹ฌ๋ฆฌ item_info ํ…Œ์ด๋ธ”๋งŒ ์กฐํšŒํ•˜๋ฉฐ, - * ์ˆ˜์ฃผ ๋“ฑ๋ก์— ํ•„์š”ํ•œ ์ปฌ๋Ÿผ๊ณผ ๊ณ„์‚ฐ ๊ณต์‹์ด ๋ฏธ๋ฆฌ ์„ค์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. - */ - -interface OrderItemRepeaterTableProps { - /** ํ˜„์žฌ ์„ ํƒ๋œ ํ’ˆ๋ชฉ ๋ชฉ๋ก */ - value: any[]; - /** ํ’ˆ๋ชฉ ๋ชฉ๋ก ๋ณ€๊ฒฝ ์‹œ ์ฝœ๋ฐฑ */ - onChange: (items: any[]) => void; - /** ๋น„ํ™œ์„ฑํ™” ์—ฌ๋ถ€ */ - disabled?: boolean; -} - -// ์ˆ˜์ฃผ ๋“ฑ๋ก ์ „์šฉ ์ปฌ๋Ÿผ ์„ค์ • (๊ณ ์ •) -const ORDER_COLUMNS: RepeaterColumnConfig[] = [ - { - field: "item_number", - label: "ํ’ˆ๋ฒˆ", - editable: false, - width: "120px", - }, - { - field: "item_name", - label: "ํ’ˆ๋ช…", - editable: false, - width: "180px", - }, - { - field: "specification", - label: "๊ทœ๊ฒฉ", - editable: false, - width: "150px", - }, - { - field: "material", - label: "์žฌ์งˆ", - editable: false, - width: "120px", - }, - { - field: "quantity", - label: "์ˆ˜๋Ÿ‰", - type: "number", - editable: true, - required: true, - defaultValue: 1, - width: "100px", - }, - { - field: "selling_price", - label: "๋‹จ๊ฐ€", - type: "number", - editable: true, - required: true, - width: "120px", - }, - { - field: "amount", - label: "๊ธˆ์•ก", - type: "number", - editable: false, - calculated: true, - width: "120px", - }, - { - field: "order_date", - label: "์ˆ˜์ฃผ์ผ", - type: "date", - editable: true, - width: "130px", - }, - { - field: "delivery_date", - label: "๋‚ฉ๊ธฐ์ผ", - type: "date", - editable: true, - width: "130px", - }, -]; - -// ์ˆ˜์ฃผ ๋“ฑ๋ก ์ „์šฉ ๊ณ„์‚ฐ ๊ณต์‹ (๊ณ ์ •) -const ORDER_CALCULATION_RULES: CalculationRule[] = [ - { - result: "amount", - formula: "quantity * selling_price", - dependencies: ["quantity", "selling_price"], - }, -]; - -export function OrderItemRepeaterTable({ - value, - onChange, - disabled = false, -}: OrderItemRepeaterTableProps) { - return ( - - ); -} - diff --git a/frontend/components/order/OrderRegistrationModal.tsx b/frontend/components/order/OrderRegistrationModal.tsx deleted file mode 100644 index e47e124f..00000000 --- a/frontend/components/order/OrderRegistrationModal.tsx +++ /dev/null @@ -1,572 +0,0 @@ -"use client"; - -import React, { useState } from "react"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { Button } from "@/components/ui/button"; -import { Label } from "@/components/ui/label"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { OrderCustomerSearch } from "./OrderCustomerSearch"; -import { OrderItemRepeaterTable } from "./OrderItemRepeaterTable"; -import { toast } from "sonner"; -import { apiClient } from "@/lib/api/client"; - -interface OrderRegistrationModalProps { - open: boolean; - onOpenChange: (open: boolean) => void; - onSuccess?: () => void; -} - -export function OrderRegistrationModal({ - open, - onOpenChange, - onSuccess, -}: OrderRegistrationModalProps) { - // ์ž…๋ ฅ ๋ฐฉ์‹ - const [inputMode, setInputMode] = useState("customer_first"); - - // ํŒ๋งค ์œ ํ˜• (๊ตญ๋‚ด/ํ•ด์™ธ) - const [salesType, setSalesType] = useState("domestic"); - - // ๋‹จ๊ฐ€ ๊ธฐ์ค€ (๊ธฐ์ค€๋‹จ๊ฐ€/๊ฑฐ๋ž˜์ฒ˜๋ณ„๋‹จ๊ฐ€) - const [priceType, setPriceType] = useState("standard"); - - // ํผ ๋ฐ์ดํ„ฐ - const [formData, setFormData] = useState({ - customerCode: "", - customerName: "", - contactPerson: "", - deliveryDestination: "", - deliveryAddress: "", - deliveryDate: "", - memo: "", - // ๋ฌด์—ญ ์ •๋ณด (ํ•ด์™ธ ํŒ๋งค ์‹œ) - incoterms: "", - paymentTerms: "", - currency: "KRW", - portOfLoading: "", - portOfDischarge: "", - hsCode: "", - }); - - // ์„ ํƒ๋œ ํ’ˆ๋ชฉ ๋ชฉ๋ก - const [selectedItems, setSelectedItems] = useState([]); - - // ๋‚ฉ๊ธฐ์ผ ์ผ๊ด„ ์ ์šฉ ํ”Œ๋ž˜๊ทธ (๋”ฑ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰) - const [isDeliveryDateApplied, setIsDeliveryDateApplied] = useState(false); - - // ์ €์žฅ ์ค‘ - const [isSaving, setIsSaving] = useState(false); - - // ์ €์žฅ ์ฒ˜๋ฆฌ - const handleSave = async () => { - try { - // ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ - if (!formData.customerCode) { - toast.error("๊ฑฐ๋ž˜์ฒ˜๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”"); - return; - } - - if (selectedItems.length === 0) { - toast.error("ํ’ˆ๋ชฉ์„ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”"); - return; - } - - setIsSaving(true); - - // ์ˆ˜์ฃผ ๋“ฑ๋ก API ํ˜ธ์ถœ - const orderData: any = { - inputMode, - salesType, - priceType, - customerCode: formData.customerCode, - contactPerson: formData.contactPerson, - deliveryDestination: formData.deliveryDestination, - deliveryAddress: formData.deliveryAddress, - deliveryDate: formData.deliveryDate, - items: selectedItems, - memo: formData.memo, - }; - - // ํ•ด์™ธ ํŒ๋งค ์‹œ ๋ฌด์—ญ ์ •๋ณด ์ถ”๊ฐ€ - if (salesType === "export") { - orderData.tradeInfo = { - incoterms: formData.incoterms, - paymentTerms: formData.paymentTerms, - currency: formData.currency, - portOfLoading: formData.portOfLoading, - portOfDischarge: formData.portOfDischarge, - hsCode: formData.hsCode, - }; - } - - const response = await apiClient.post("/orders", orderData); - - if (response.data.success) { - toast.success("์ˆ˜์ฃผ๊ฐ€ ๋“ฑ๋ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค"); - onOpenChange(false); - onSuccess?.(); - - // ํผ ์ดˆ๊ธฐํ™” - resetForm(); - } else { - toast.error(response.data.message || "์ˆ˜์ฃผ ๋“ฑ๋ก์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค"); - } - } catch (error: any) { - console.error("์ˆ˜์ฃผ ๋“ฑ๋ก ์˜ค๋ฅ˜:", error); - toast.error( - error.response?.data?.message || "์ˆ˜์ฃผ ๋“ฑ๋ก ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค" - ); - } finally { - setIsSaving(false); - } - }; - - // ์ทจ์†Œ ์ฒ˜๋ฆฌ - const handleCancel = () => { - onOpenChange(false); - resetForm(); - }; - - // ํผ ์ดˆ๊ธฐํ™” - const resetForm = () => { - setInputMode("customer_first"); - setSalesType("domestic"); - setPriceType("standard"); - setFormData({ - customerCode: "", - customerName: "", - contactPerson: "", - deliveryDestination: "", - deliveryAddress: "", - deliveryDate: "", - memo: "", - incoterms: "", - paymentTerms: "", - currency: "KRW", - portOfLoading: "", - portOfDischarge: "", - hsCode: "", - }); - setSelectedItems([]); - setIsDeliveryDateApplied(false); // ํ”Œ๋ž˜๊ทธ ์ดˆ๊ธฐํ™” - }; - - // ํ’ˆ๋ชฉ ๋ชฉ๋ก ๋ณ€๊ฒฝ ํ•ธ๋“ค๋Ÿฌ (๋‚ฉ๊ธฐ์ผ ์ผ๊ด„ ์ ์šฉ ๋กœ์ง ํฌํ•จ) - const handleItemsChange = (newItems: any[]) => { - // 1๏ธโƒฃ ํ”Œ๋ž˜๊ทธ๊ฐ€ ์ด๋ฏธ true๋ฉด ๊ทธ๋ƒฅ ์—…๋ฐ์ดํŠธ๋งŒ (์ผ๊ด„ ์ ์šฉ ์™„๋ฃŒ ์ƒํƒœ) - if (isDeliveryDateApplied) { - setSelectedItems(newItems); - return; - } - - // 2๏ธโƒฃ ํ’ˆ๋ชฉ์ด ์—†์œผ๋ฉด ๊ทธ๋ƒฅ ์—…๋ฐ์ดํŠธ - if (newItems.length === 0) { - setSelectedItems(newItems); - return; - } - - // 3๏ธโƒฃ ํ˜„์žฌ ์ƒํƒœ: ๋‚ฉ๊ธฐ์ผ์ด ์žˆ๋Š” ํ–‰๊ณผ ์—†๋Š” ํ–‰ ๊ฐœ์ˆ˜ ์ฒดํฌ - const itemsWithDate = newItems.filter((item) => item.delivery_date); - const itemsWithoutDate = newItems.filter((item) => !item.delivery_date); - - // 4๏ธโƒฃ ์กฐ๊ฑด: ์ •ํ™•ํžˆ 1๊ฐœ๋งŒ ๋‚ ์งœ๊ฐ€ ์žˆ๊ณ , ๋‚˜๋จธ์ง€๋Š” ๋ชจ๋‘ ๋น„์–ด์žˆ์„ ๋•Œ ์ผ๊ด„ ์ ์šฉ - if (itemsWithDate.length === 1 && itemsWithoutDate.length > 0) { - // 5๏ธโƒฃ ์ „์ฒด ์ผ๊ด„ ์ ์šฉ - const selectedDate = itemsWithDate[0].delivery_date; - const updatedItems = newItems.map((item) => ({ - ...item, - delivery_date: selectedDate, // ๋ชจ๋“  ํ–‰์— ๋™์ผํ•œ ๋‚ฉ๊ธฐ์ผ ์ ์šฉ - })); - - setSelectedItems(updatedItems); - setIsDeliveryDateApplied(true); // ํ”Œ๋ž˜๊ทธ ํ™œ์„ฑํ™” (๋‹ค์Œ๋ถ€ํ„ฐ๋Š” ์ผ๊ด„ ์ ์šฉ ์•ˆ ํ•จ) - - console.log("โœ… ๋‚ฉ๊ธฐ์ผ ์ผ๊ด„ ์ ์šฉ ์™„๋ฃŒ:", selectedDate); - console.log(` - ๋Œ€์ƒ: ${itemsWithoutDate.length}๊ฐœ ํ–‰์— ${selectedDate} ์ ์šฉ`); - } else { - // ๊ทธ๋ƒฅ ์—…๋ฐ์ดํŠธ - setSelectedItems(newItems); - } - }; - - // ์ „์ฒด ๊ธˆ์•ก ๊ณ„์‚ฐ - const totalAmount = selectedItems.reduce( - (sum, item) => sum + (item.amount || 0), - 0 - ); - - return ( - - - - ์ˆ˜์ฃผ ๋“ฑ๋ก - - ์ƒˆ๋กœ์šด ์ˆ˜์ฃผ๋ฅผ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค - - - -
- {/* ์ƒ๋‹จ ์…€๋ ‰ํŠธ ๋ฐ•์Šค 3๊ฐœ */} -
- {/* ์ž…๋ ฅ ๋ฐฉ์‹ */} -
- - -
- - {/* ํŒ๋งค ์œ ํ˜• */} -
- - -
- - {/* ๋‹จ๊ฐ€ ๊ธฐ์ค€ */} -
- - -
-
- - {/* ๊ฑฐ๋ž˜์ฒ˜ ์ •๋ณด (ํ•ญ์ƒ ํ‘œ์‹œ) */} - {inputMode === "customer_first" && ( -
-
- ๐Ÿข - ๊ฑฐ๋ž˜์ฒ˜ ์ •๋ณด -
- -
- {/* ๊ฑฐ๋ž˜์ฒ˜ */} -
- - { - setFormData({ - ...formData, - customerCode: code || "", - customerName: fullData?.customer_name || "", - }); - }} - /> -
- - {/* ๋‹ด๋‹น์ž */} -
- - - setFormData({ ...formData, contactPerson: e.target.value }) - } - className="flex h-8 w-full rounded-md border border-input bg-background px-3 py-2 text-xs ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 sm:h-10 sm:text-sm" - /> -
- - {/* ๋‚ฉํ’ˆ์ฒ˜ */} -
- - - setFormData({ ...formData, deliveryDestination: e.target.value }) - } - className="flex h-8 w-full rounded-md border border-input bg-background px-3 py-2 text-xs ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 sm:h-10 sm:text-sm" - /> -
- - {/* ๋‚ฉํ’ˆ์žฅ์†Œ */} -
- - - setFormData({ ...formData, deliveryAddress: e.target.value }) - } - className="flex h-8 w-full rounded-md border border-input bg-background px-3 py-2 text-xs ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 sm:h-10 sm:text-sm" - /> -
-
-
- )} - - {inputMode === "quotation" && ( -
-
- - -
-
- )} - - {inputMode === "unit_price" && ( -
-
- - -
-
- )} - - {/* ์ถ”๊ฐ€๋œ ํ’ˆ๋ชฉ */} -
- - -
- - {/* ์ „์ฒด ๊ธˆ์•ก ํ‘œ์‹œ */} - {selectedItems.length > 0 && ( -
-
- ์ „์ฒด ๊ธˆ์•ก: {totalAmount.toLocaleString()}์› -
-
- )} - - {/* ๋ฌด์—ญ ์ •๋ณด (ํ•ด์™ธ ํŒ๋งค ์‹œ์—๋งŒ ํ‘œ์‹œ) */} - {salesType === "export" && ( -
-
- ๐ŸŒ - ๋ฌด์—ญ ์ •๋ณด -
- -
- {/* ์ธ์ฝ”ํ…€์ฆˆ */} -
- - -
- - {/* ๊ฒฐ์ œ ์กฐ๊ฑด */} -
- - -
- - {/* ํ†ตํ™” */} -
- - -
-
- -
- {/* ์„ ์ ํ•ญ */} -
- - - setFormData({ ...formData, portOfLoading: e.target.value }) - } - className="flex h-8 w-full rounded-md border border-input bg-background px-3 py-2 text-xs ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 sm:h-10 sm:text-sm" - /> -
- - {/* ๋„์ฐฉํ•ญ */} -
- - - setFormData({ ...formData, portOfDischarge: e.target.value }) - } - className="flex h-8 w-full rounded-md border border-input bg-background px-3 py-2 text-xs ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 sm:h-10 sm:text-sm" - /> -
- - {/* HS Code */} -
- - - setFormData({ ...formData, hsCode: e.target.value }) - } - className="flex h-8 w-full rounded-md border border-input bg-background px-3 py-2 text-xs ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 sm:h-10 sm:text-sm" - /> -
-
-
- )} - - {/* ๋ฉ”๋ชจ */} -
- -