diff --git a/backend-node/src/services/dynamicFormService.ts b/backend-node/src/services/dynamicFormService.ts index 4d33dc1c..e9485620 100644 --- a/backend-node/src/services/dynamicFormService.ts +++ b/backend-node/src/services/dynamicFormService.ts @@ -811,9 +811,39 @@ export class DynamicFormService { const primaryKeyColumn = primaryKeys[0]; console.log(`๐Ÿ”‘ ํ…Œ์ด๋ธ” ${tableName}์˜ ๊ธฐ๋ณธํ‚ค: ${primaryKeyColumn}`); - // ๋™์  UPDATE SQL ์ƒ์„ฑ (๋ณ€๊ฒฝ๋œ ํ•„๋“œ๋งŒ) + // ๐Ÿ†• ์ปฌ๋Ÿผ ํƒ€์ž… ์กฐํšŒ (ํƒ€์ž… ์บ์ŠคํŒ…์šฉ) + const columnTypesQuery = ` + SELECT column_name, data_type + FROM information_schema.columns + WHERE table_name = $1 AND table_schema = 'public' + `; + const columnTypesResult = await query<{ column_name: string; data_type: string }>( + columnTypesQuery, + [tableName] + ); + const columnTypes: Record = {}; + columnTypesResult.forEach((row) => { + columnTypes[row.column_name] = row.data_type; + }); + + console.log("๐Ÿ“Š ์ปฌ๋Ÿผ ํƒ€์ž… ์ •๋ณด:", columnTypes); + + // ๐Ÿ†• ๋™์  UPDATE SQL ์ƒ์„ฑ (ํƒ€์ž… ์บ์ŠคํŒ… ํฌํ•จ) const setClause = Object.keys(changedFields) - .map((key, index) => `${key} = $${index + 1}`) + .map((key, index) => { + const dataType = columnTypes[key]; + // ์ˆซ์ž ํƒ€์ž…์ธ ๊ฒฝ์šฐ ๋ช…์‹œ์  ์บ์ŠคํŒ… + if (dataType === 'integer' || dataType === 'bigint' || dataType === 'smallint') { + return `${key} = $${index + 1}::integer`; + } else if (dataType === 'numeric' || dataType === 'decimal' || dataType === 'real' || dataType === 'double precision') { + return `${key} = $${index + 1}::numeric`; + } else if (dataType === 'boolean') { + return `${key} = $${index + 1}::boolean`; + } else { + // ๋ฌธ์ž์—ด ํƒ€์ž…์€ ์บ์ŠคํŒ… ๋ถˆํ•„์š” + return `${key} = $${index + 1}`; + } + }) .join(", "); const values: any[] = Object.values(changedFields); diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx index 3280891f..f9b803b2 100644 --- a/frontend/components/screen/EditModal.tsx +++ b/frontend/components/screen/EditModal.tsx @@ -320,43 +320,24 @@ export const EditModal: React.FC = ({ className }) => { let updatedCount = 0; let deletedCount = 0; - // ๐Ÿ†• sales_order_mng ํ…Œ์ด๋ธ”์˜ ์‹ค์ œ ์ปฌ๋Ÿผ๋งŒ ํฌํ•จ (์กฐ์ธ๋œ ์ปฌ๋Ÿผ ์ œ์™ธ) - const salesOrderColumns = [ - "id", - "order_no", - "customer_code", - "customer_name", - "order_date", - "delivery_date", - "item_code", - "quantity", - "unit_price", - "amount", - "status", - "notes", - "created_at", - "updated_at", - "company_code", - ]; - // 1๏ธโƒฃ ์‹ ๊ทœ ํ’ˆ๋ชฉ ์ถ”๊ฐ€ (id๊ฐ€ ์—†๋Š” ํ•ญ๋ชฉ) for (const currentData of groupData) { if (!currentData.id) { console.log("โž• ์‹ ๊ทœ ํ’ˆ๋ชฉ ์ถ”๊ฐ€:", currentData); + console.log("๐Ÿ“‹ [์‹ ๊ทœ ํ’ˆ๋ชฉ] currentData ํ‚ค ๋ชฉ๋ก:", Object.keys(currentData)); - // ์‹ค์ œ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ๋งŒ ์ถ”์ถœ - const insertData: Record = {}; - Object.keys(currentData).forEach((key) => { - if (salesOrderColumns.includes(key) && key !== "id") { - insertData[key] = currentData[key]; - } - }); + // ๐Ÿ†• ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จ (id ์ œ์™ธ) + const insertData: Record = { ...currentData }; + console.log("๐Ÿ“ฆ [์‹ ๊ทœ ํ’ˆ๋ชฉ] ๋ณต์‚ฌ ์งํ›„ insertData:", insertData); + console.log("๐Ÿ“‹ [์‹ ๊ทœ ํ’ˆ๋ชฉ] insertData ํ‚ค ๋ชฉ๋ก:", Object.keys(insertData)); + + delete insertData.id; // id๋Š” ์ž๋™ ์ƒ์„ฑ๋˜๋ฏ€๋กœ ์ œ๊ฑฐ // ๐Ÿ†• groupByColumns์˜ ๊ฐ’์„ ๊ฐ•์ œ๋กœ ํฌํ•จ (order_no ๋“ฑ) if (modalState.groupByColumns && modalState.groupByColumns.length > 0) { modalState.groupByColumns.forEach((colName) => { - // ๊ธฐ์กด ํ’ˆ๋ชฉ(groupData[0])์—์„œ groupByColumns ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ - const referenceData = originalGroupData[0] || groupData.find(item => item.id); + // ๊ธฐ์กด ํ’ˆ๋ชฉ(originalGroupData[0])์—์„œ groupByColumns ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ + const referenceData = originalGroupData[0] || groupData.find((item) => item.id); if (referenceData && referenceData[colName]) { insertData[colName] = referenceData[colName]; console.log(`๐Ÿ”‘ [์‹ ๊ทœ ํ’ˆ๋ชฉ] ${colName} ๊ฐ’ ์ถ”๊ฐ€:`, referenceData[colName]); @@ -364,7 +345,31 @@ export const EditModal: React.FC = ({ className }) => { }); } + // ๐Ÿ†• ๊ณตํ†ต ํ•„๋“œ ์ถ”๊ฐ€ (๊ฑฐ๋ž˜์ฒ˜, ๋‹ด๋‹น์ž, ๋‚ฉํ’ˆ์ฒ˜, ๋ฉ”๋ชจ ๋“ฑ) + // formData์—์„œ ํ’ˆ๋ชฉ๋ณ„ ํ•„๋“œ๊ฐ€ ์•„๋‹Œ ๊ณตํ†ต ํ•„๋“œ๋ฅผ ๋ณต์‚ฌ + const commonFields = [ + 'partner_id', // ๊ฑฐ๋ž˜์ฒ˜ + 'manager_id', // ๋‹ด๋‹น์ž + 'delivery_partner_id', // ๋‚ฉํ’ˆ์ฒ˜ + 'delivery_address', // ๋‚ฉํ’ˆ์žฅ์†Œ + 'memo', // ๋ฉ”๋ชจ + 'order_date', // ์ฃผ๋ฌธ์ผ + 'due_date', // ๋‚ฉ๊ธฐ์ผ + 'shipping_method', // ๋ฐฐ์†ก๋ฐฉ๋ฒ• + 'status', // ์ƒํƒœ + 'sales_type', // ์˜์—…์œ ํ˜• + ]; + + commonFields.forEach((fieldName) => { + // formData์— ๊ฐ’์ด ์žˆ์œผ๋ฉด ์ถ”๊ฐ€ + if (formData[fieldName] !== undefined && formData[fieldName] !== null) { + insertData[fieldName] = formData[fieldName]; + console.log(`๐Ÿ”— [๊ณตํ†ต ํ•„๋“œ] ${fieldName} ๊ฐ’ ์ถ”๊ฐ€:`, formData[fieldName]); + } + }); + console.log("๐Ÿ“ฆ [์‹ ๊ทœ ํ’ˆ๋ชฉ] ์ตœ์ข… insertData:", insertData); + console.log("๐Ÿ“‹ [์‹ ๊ทœ ํ’ˆ๋ชฉ] ์ตœ์ข… insertData ํ‚ค ๋ชฉ๋ก:", Object.keys(insertData)); try { const response = await dynamicFormApi.saveFormData({ @@ -398,16 +403,32 @@ export const EditModal: React.FC = ({ className }) => { continue; } - // ๋ณ€๊ฒฝ๋œ ํ•„๋“œ๋งŒ ์ถ”์ถœ + // ๐Ÿ†• ๊ฐ’ ์ •๊ทœํ™” ํ•จ์ˆ˜ (ํƒ€์ž… ํ†ต์ผ) + const normalizeValue = (val: any): any => { + if (val === null || val === undefined || val === "") return null; + if (typeof val === "string" && !isNaN(Number(val))) { + // ์ˆซ์ž๋กœ ๋ณ€ํ™˜ ๊ฐ€๋Šฅํ•œ ๋ฌธ์ž์—ด์€ ์ˆซ์ž๋กœ + return Number(val); + } + return val; + }; + + // ๋ณ€๊ฒฝ๋œ ํ•„๋“œ๋งŒ ์ถ”์ถœ (id ์ œ์™ธ) const changedData: Record = {}; Object.keys(currentData).forEach((key) => { - // sales_order_mng ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ๋งŒ ์ฒ˜๋ฆฌ (์กฐ์ธ ์ปฌ๋Ÿผ ์ œ์™ธ) - if (!salesOrderColumns.includes(key)) { + // id๋Š” ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€ + if (key === "id") { return; } - if (currentData[key] !== originalItemData[key]) { - changedData[key] = currentData[key]; + // ๐Ÿ†• ํƒ€์ž… ์ •๊ทœํ™” ํ›„ ๋น„๊ต + const currentValue = normalizeValue(currentData[key]); + const originalValue = normalizeValue(originalItemData[key]); + + // ๊ฐ’์ด ๋ณ€๊ฒฝ๋œ ๊ฒฝ์šฐ๋งŒ ํฌํ•จ + if (currentValue !== originalValue) { + console.log(`๐Ÿ” [ํ’ˆ๋ชฉ ์ˆ˜์ • ๊ฐ์ง€] ${key}: ${originalValue} โ†’ ${currentValue}`); + changedData[key] = currentData[key]; // ์›๋ณธ ๊ฐ’ ์‚ฌ์šฉ (๋ฌธ์ž์—ด ๊ทธ๋Œ€๋กœ) } }); @@ -677,6 +698,8 @@ export const EditModal: React.FC = ({ className }) => { isInModal={true} // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ๋ฅผ ModalRepeaterTable์— ์ „๋‹ฌ groupedData={groupData.length > 0 ? groupData : undefined} + // ๐Ÿ†• ์ˆ˜์ • ๋ชจ๋‹ฌ์—์„œ ์ฝ๊ธฐ ์ „์šฉ ํ•„๋“œ ์ง€์ • (์ˆ˜์ฃผ๋ฒˆํ˜ธ, ๊ฑฐ๋ž˜์ฒ˜) + disabledFields={["order_no", "partner_id"]} /> ); })} diff --git a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx index fb5046c3..aa46ed40 100644 --- a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx +++ b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx @@ -48,6 +48,8 @@ interface InteractiveScreenViewerProps { companyCode?: string; // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ (EditModal์—์„œ ์ „๋‹ฌ) groupedData?: Record[]; + // ๐Ÿ†• ๋น„ํ™œ์„ฑํ™”ํ•  ํ•„๋“œ ๋ชฉ๋ก (EditModal์—์„œ ์ „๋‹ฌ) + disabledFields?: string[]; // ๐Ÿ†• EditModal ๋‚ด๋ถ€์ธ์ง€ ์—ฌ๋ถ€ (button-primary๊ฐ€ EditModal์˜ handleSave ์‚ฌ์šฉํ•˜๋„๋ก) isInModal?: boolean; } @@ -66,6 +68,7 @@ export const InteractiveScreenViewerDynamic: React.FC { const { isPreviewMode } = useScreenPreview(); // ํ”„๋ฆฌ๋ทฐ ๋ชจ๋“œ ํ™•์ธ @@ -341,6 +344,8 @@ export const InteractiveScreenViewerDynamic: React.FC { diff --git a/frontend/lib/registry/DynamicComponentRenderer.tsx b/frontend/lib/registry/DynamicComponentRenderer.tsx index bf2b6ecb..cf6037eb 100644 --- a/frontend/lib/registry/DynamicComponentRenderer.tsx +++ b/frontend/lib/registry/DynamicComponentRenderer.tsx @@ -110,6 +110,8 @@ export interface DynamicComponentRendererProps { selectedRows?: any[]; // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ (EditModal โ†’ ModalRepeaterTable) groupedData?: Record[]; + // ๐Ÿ†• ๋น„ํ™œ์„ฑํ™”ํ•  ํ•„๋“œ ๋ชฉ๋ก (EditModal โ†’ ๊ฐ ์ปดํฌ๋„ŒํŠธ) + disabledFields?: string[]; selectedRowsData?: any[]; onSelectedRowsChange?: (selectedRows: any[], selectedRowsData: any[], sortBy?: string, sortOrder?: "asc" | "desc", columnOrder?: string[], tableDisplayData?: any[]) => void; // ํ…Œ์ด๋ธ” ์ •๋ ฌ ์ •๋ณด (์—‘์…€ ๋‹ค์šด๋กœ๋“œ์šฉ) @@ -168,6 +170,9 @@ export const DynamicComponentRenderer: React.FC = } }; + // ๐Ÿ†• disabledFields ์ฒดํฌ + const isFieldDisabled = props.disabledFields?.includes(columnName) || (component as any).readonly; + return ( = onChange={handleChange} placeholder={component.componentConfig?.placeholder || "์„ ํƒํ•˜์„ธ์š”"} required={(component as any).required} - disabled={(component as any).readonly} + disabled={isFieldDisabled} className="w-full" /> ); @@ -271,6 +276,7 @@ export const DynamicComponentRenderer: React.FC = onConfigChange, isPreview, autoGeneration, + disabledFields, // ๐Ÿ†• ๋น„ํ™œ์„ฑํ™” ํ•„๋“œ ๋ชฉ๋ก ...restProps } = props; @@ -368,7 +374,8 @@ export const DynamicComponentRenderer: React.FC = mode, isInModal, readonly: component.readonly, - disabled: component.readonly, + // ๐Ÿ†• disabledFields ์ฒดํฌ ๋˜๋Š” ๊ธฐ์กด readonly + disabled: disabledFields?.includes(fieldName) || component.readonly, originalData, allComponents, onUpdateLayout, diff --git a/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx b/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx index 9709b620..735fac6d 100644 --- a/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx +++ b/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx @@ -154,18 +154,18 @@ export function ConditionalSectionViewer({ }} > + /> ); })} diff --git a/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx b/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx index 3941a89f..59ce35a8 100644 --- a/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx +++ b/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx @@ -195,13 +195,18 @@ export function ModalRepeaterTableComponent({ const columnName = component?.columnName; const value = (columnName && formData?.[columnName]) || componentConfig?.value || propValue || []; - // โœ… onChange ๋ž˜ํผ (๊ธฐ์กด onChange ์ฝœ๋ฐฑ๋งŒ ํ˜ธ์ถœ, formData๋Š” beforeFormSave์—์„œ ์ฒ˜๋ฆฌ) + // โœ… onChange ๋ž˜ํผ (๊ธฐ์กด onChange ์ฝœ๋ฐฑ + onFormDataChange ํ˜ธ์ถœ) const handleChange = (newData: any[]) => { // ๊ธฐ์กด onChange ์ฝœ๋ฐฑ ํ˜ธ์ถœ (ํ˜ธํ™˜์„ฑ) const externalOnChange = componentConfig?.onChange || propOnChange; if (externalOnChange) { externalOnChange(newData); } + + // ๐Ÿ†• onFormDataChange ํ˜ธ์ถœํ•˜์—ฌ EditModal์˜ groupData ์—…๋ฐ์ดํŠธ + if (onFormDataChange && columnName) { + onFormDataChange(columnName, newData); + } }; // uniqueField ์ž๋™ ๋ณด์ •: order_no๋Š” item_info ํ…Œ์ด๋ธ”์— ์—†์œผ๋ฏ€๋กœ item_number๋กœ ๋ณ€๊ฒฝ