diff --git a/backend-node/src/controllers/tableManagementController.ts b/backend-node/src/controllers/tableManagementController.ts index 2dfe0770..66c70a77 100644 --- a/backend-node/src/controllers/tableManagementController.ts +++ b/backend-node/src/controllers/tableManagementController.ts @@ -2141,3 +2141,4 @@ export async function multiTableSave( client.release(); } } + diff --git a/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx b/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx index 1a8012de..360a1585 100644 --- a/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx +++ b/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx @@ -99,25 +99,99 @@ export function RepeatScreenModalComponent({ contentRowId: string; } | null>(null); + // ๐Ÿ†• v3.13: ์™ธ๋ถ€์—์„œ ์ €์žฅ ํŠธ๋ฆฌ๊ฑฐ ๊ฐ€๋Šฅํ•˜๋„๋ก ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ถ”๊ฐ€ + useEffect(() => { + const handleTriggerSave = async (event: Event) => { + if (!(event instanceof CustomEvent)) return; + + console.log("[RepeatScreenModal] triggerRepeatScreenModalSave ์ด๋ฒคํŠธ ์ˆ˜์‹ "); + + try { + setIsSaving(true); + + // ๊ธฐ์กด ๋ฐ์ดํ„ฐ ์ €์žฅ + if (cardMode === "withTable") { + await saveGroupedData(); + } else { + await saveSimpleData(); + } + + // ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์ €์žฅ + await saveExternalTableData(); + + // ์—ฐ๋™ ์ €์žฅ ์ฒ˜๋ฆฌ (syncSaves) + await processSyncSaves(); + + console.log("[RepeatScreenModal] ์™ธ๋ถ€ ํŠธ๋ฆฌ๊ฑฐ ์ €์žฅ ์™„๋ฃŒ"); + + // ์ €์žฅ ์™„๋ฃŒ ์ด๋ฒคํŠธ ๋ฐœ์ƒ + window.dispatchEvent(new CustomEvent("repeatScreenModalSaveComplete", { + detail: { success: true } + })); + + // ์„ฑ๊ณต ์ฝœ๋ฐฑ ์‹คํ–‰ + if (event.detail?.onSuccess) { + event.detail.onSuccess(); + } + } catch (error: any) { + console.error("[RepeatScreenModal] ์™ธ๋ถ€ ํŠธ๋ฆฌ๊ฑฐ ์ €์žฅ ์‹คํŒจ:", error); + + // ์ €์žฅ ์‹คํŒจ ์ด๋ฒคํŠธ ๋ฐœ์ƒ + window.dispatchEvent(new CustomEvent("repeatScreenModalSaveComplete", { + detail: { success: false, error: error.message } + })); + + // ์‹คํŒจ ์ฝœ๋ฐฑ ์‹คํ–‰ + if (event.detail?.onError) { + event.detail.onError(error); + } + } finally { + setIsSaving(false); + } + }; + + window.addEventListener("triggerRepeatScreenModalSave", handleTriggerSave as EventListener); + return () => { + window.removeEventListener("triggerRepeatScreenModalSave", handleTriggerSave as EventListener); + }; + }, [cardMode, groupedCardsData, externalTableData, contentRows]); + // ๐Ÿ†• v3.9: beforeFormSave ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ - ButtonPrimary ์ €์žฅ ์‹œ externalTableData๋ฅผ formData์— ๋ณ‘ํ•ฉ useEffect(() => { const handleBeforeFormSave = (event: Event) => { if (!(event instanceof CustomEvent) || !event.detail?.formData) return; console.log("[RepeatScreenModal] beforeFormSave ์ด๋ฒคํŠธ ์ˆ˜์‹ "); + console.log("[RepeatScreenModal] beforeFormSave - externalTableData:", externalTableData); + console.log("[RepeatScreenModal] beforeFormSave - groupedCardsData:", groupedCardsData.length, "๊ฐœ ์นด๋“œ"); // ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ์—์„œ dirty ํ–‰๋งŒ ์ถ”์ถœํ•˜์—ฌ ์ €์žฅ ๋ฐ์ดํ„ฐ ์ค€๋น„ const saveDataByTable: Record = {}; for (const [key, rows] of Object.entries(externalTableData)) { + // key ํ˜•์‹: cardId-contentRowId + const keyParts = key.split("-"); + const cardId = keyParts.slice(0, -1).join("-"); // contentRowId๋ฅผ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€๊ฐ€ cardId + // contentRow ์ฐพ๊ธฐ const contentRow = contentRows.find((r) => key.includes(r.id)); if (!contentRow?.tableDataSource?.enabled) continue; + // ๐Ÿ†• v3.13: ํ•ด๋‹น ์นด๋“œ์˜ ๋Œ€ํ‘œ ๋ฐ์ดํ„ฐ ์ฐพ๊ธฐ (joinConditions์˜ targetKey ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด) + const card = groupedCardsData.find((c) => c._cardId === cardId); + const representativeData = card?._representativeData || {}; + const targetTable = contentRow.tableCrud?.targetTable || contentRow.tableDataSource.sourceTable; - // dirty ํ–‰๋งŒ ํ•„ํ„ฐ๋ง (์‚ญ์ œ๋œ ํ–‰ ์ œ์™ธ) - const dirtyRows = rows.filter((row) => row._isDirty && !row._isDeleted); + // dirty ํ–‰ ๋˜๋Š” ์ƒˆ๋กœ์šด ํ–‰ ํ•„ํ„ฐ๋ง (์‚ญ์ œ๋œ ํ–‰ ์ œ์™ธ) + // ๐Ÿ†• v3.13: _isNew ํ–‰๋„ ํฌํ•จ (์ƒˆ๋กœ ์ถ”๊ฐ€๋œ ํ–‰์€ _isDirty๊ฐ€ ์—†์„ ์ˆ˜ ์žˆ์Œ) + const dirtyRows = rows.filter((row) => (row._isDirty || row._isNew) && !row._isDeleted); + + console.log(`[RepeatScreenModal] beforeFormSave - ${targetTable} ํ–‰ ํ•„ํ„ฐ๋ง:`, { + totalRows: rows.length, + dirtyRows: dirtyRows.length, + rowDetails: rows.map(r => ({ _isDirty: r._isDirty, _isNew: r._isNew, _isDeleted: r._isDeleted })) + }); if (dirtyRows.length === 0) continue; @@ -126,8 +200,9 @@ export function RepeatScreenModalComponent({ .filter((col) => col.editable) .map((col) => col.field); - const joinKeys = (contentRow.tableDataSource.joinConditions || []) - .map((cond) => cond.sourceKey); + // ๐Ÿ†• v3.13: joinConditions์—์„œ sourceKey (์ €์žฅ ๋Œ€์ƒ ํ…Œ์ด๋ธ”์˜ FK ์ปฌ๋Ÿผ) ์ถ”์ถœ + const joinConditions = contentRow.tableDataSource.joinConditions || []; + const joinKeys = joinConditions.map((cond) => cond.sourceKey); const allowedFields = [...new Set([...editableFields, ...joinKeys])]; @@ -145,6 +220,17 @@ export function RepeatScreenModalComponent({ } } + // ๐Ÿ†• v3.13: joinConditions๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ FK ๊ฐ’ ์ž๋™ ์ฑ„์šฐ๊ธฐ + // ์˜ˆ: sales_order_id (sourceKey) = card์˜ id (targetKey) + for (const joinCond of joinConditions) { + const { sourceKey, targetKey } = joinCond; + // sourceKey๊ฐ€ ์ €์žฅ ๋ฐ์ดํ„ฐ์— ์—†๊ฑฐ๋‚˜ null์ธ ๊ฒฝ์šฐ, ์นด๋“œ์˜ ๋Œ€ํ‘œ ๋ฐ์ดํ„ฐ์—์„œ targetKey ๊ฐ’์„ ๊ฐ€์ ธ์˜ด + if (!saveData[sourceKey] && representativeData[targetKey] !== undefined) { + saveData[sourceKey] = representativeData[targetKey]; + console.log(`[RepeatScreenModal] beforeFormSave - FK ์ž๋™ ์ฑ„์šฐ๊ธฐ: ${sourceKey} = ${representativeData[targetKey]} (from card.${targetKey})`); + } + } + // _isNew ํ”Œ๋ž˜๊ทธ ์œ ์ง€ saveData._isNew = row._isNew; saveData._targetTable = targetTable; @@ -599,15 +685,17 @@ export function RepeatScreenModalComponent({ // ๊ฐ ์นด๋“œ์˜ ์ง‘๊ณ„ ์žฌ๊ณ„์‚ฐ const updatedCards = groupedCardsData.map((card) => { - // ๐Ÿ†• v3.11: ๋ชจ๋“  ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ํ–‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•ฉ์นจ + // ๐Ÿ†• v3.11: ํ…Œ์ด๋ธ” ํ–‰ ID๋ณ„๋กœ ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ๋ถ„ํ•˜์—ฌ ์ €์žฅ + const externalRowsByTableId: Record = {}; const allExternalRows: any[] = []; + for (const tableRow of tableRowsWithExternalSource) { const key = `${card._cardId}-${tableRow.id}`; // ๐Ÿ†• v3.7: ์‚ญ์ œ๋œ ํ–‰์€ ์ง‘๊ณ„์—์„œ ์ œ์™ธ const rows = (extData[key] || []).filter((row) => !row._isDeleted); + externalRowsByTableId[tableRow.id] = rows; allExternalRows.push(...rows); } - const externalRows = allExternalRows; // ์ง‘๊ณ„ ์žฌ๊ณ„์‚ฐ const newAggregations: Record = {}; @@ -622,7 +710,7 @@ export function RepeatScreenModalComponent({ if (isExternalTable) { // ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ์ง‘๊ณ„ newAggregations[agg.resultField] = calculateColumnAggregation( - externalRows, + allExternalRows, agg.sourceField || "", agg.type || "sum" ); @@ -632,12 +720,28 @@ export function RepeatScreenModalComponent({ calculateColumnAggregation(card._rows, agg.sourceField || "", agg.type || "sum"); } } else if (sourceType === "formula" && agg.formula) { + // ๐Ÿ†• v3.11: externalTableRefs ๊ธฐ๋ฐ˜์œผ๋กœ ํ•„ํ„ฐ๋ง๋œ ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ + let filteredExternalRows: any[]; + + if (agg.externalTableRefs && agg.externalTableRefs.length > 0) { + // ํŠน์ • ํ…Œ์ด๋ธ”๋งŒ ์ฐธ์กฐ + filteredExternalRows = []; + for (const tableId of agg.externalTableRefs) { + if (externalRowsByTableId[tableId]) { + filteredExternalRows.push(...externalRowsByTableId[tableId]); + } + } + } else { + // ๋ชจ๋“  ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ (๊ธฐ์กด ๋™์ž‘) + filteredExternalRows = allExternalRows; + } + // ๊ฐ€์ƒ ์ง‘๊ณ„ (์—ฐ์‚ฐ์‹) - ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ํฌํ•จํ•˜์—ฌ ์žฌ๊ณ„์‚ฐ newAggregations[agg.resultField] = evaluateFormulaWithContext( agg.formula, card._representativeData, card._rows, - externalRows, + filteredExternalRows, newAggregations // ์ด์ „ ์ง‘๊ณ„ ๊ฒฐ๊ณผ ์ฐธ์กฐ ); } @@ -660,8 +764,8 @@ export function RepeatScreenModalComponent({ }); }; - // ๐Ÿ†• v3.1: ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ํ–‰ ์ถ”๊ฐ€ - const handleAddExternalRow = (cardId: string, contentRowId: string, contentRow: CardContentRowConfig) => { + // ๐Ÿ†• v3.1: ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ํ–‰ ์ถ”๊ฐ€ (v3.13: ์ž๋™ ์ฑ„๋ฒˆ ๊ธฐ๋Šฅ ์ถ”๊ฐ€) + const handleAddExternalRow = async (cardId: string, contentRowId: string, contentRow: CardContentRowConfig) => { const key = `${cardId}-${contentRowId}`; const card = groupedCardsData.find((c) => c._cardId === cardId) || cardsData.find((c) => c._cardId === cardId); const representativeData = (card as GroupedCardData)?._representativeData || card || {}; @@ -713,6 +817,41 @@ export function RepeatScreenModalComponent({ } } + // ๐Ÿ†• v3.13: ์ž๋™ ์ฑ„๋ฒˆ ์ฒ˜๋ฆฌ + const rowNumbering = contentRow.tableCrud?.rowNumbering; + console.log("[RepeatScreenModal] ์ฑ„๋ฒˆ ์„ค์ • ํ™•์ธ:", { + tableCrud: contentRow.tableCrud, + rowNumbering, + enabled: rowNumbering?.enabled, + targetColumn: rowNumbering?.targetColumn, + numberingRuleId: rowNumbering?.numberingRuleId, + }); + if (rowNumbering?.enabled && rowNumbering.targetColumn && rowNumbering.numberingRuleId) { + try { + console.log("[RepeatScreenModal] ์ž๋™ ์ฑ„๋ฒˆ ์‹œ์ž‘:", { + targetColumn: rowNumbering.targetColumn, + numberingRuleId: rowNumbering.numberingRuleId, + }); + + // ์ฑ„๋ฒˆ API ํ˜ธ์ถœ (allocate: ์‹ค์ œ ์‹œํ€€์Šค ์ฆ๊ฐ€) + const { allocateNumberingCode } = await import("@/lib/api/numberingRule"); + const response = await allocateNumberingCode(rowNumbering.numberingRuleId); + + if (response.success && response.data) { + newRowData[rowNumbering.targetColumn] = response.data.generatedCode; + + console.log("[RepeatScreenModal] ์ž๋™ ์ฑ„๋ฒˆ ์™„๋ฃŒ:", { + column: rowNumbering.targetColumn, + generatedCode: response.data.generatedCode, + }); + } else { + console.warn("[RepeatScreenModal] ์ฑ„๋ฒˆ ์‹คํŒจ:", response); + } + } catch (error) { + console.error("[RepeatScreenModal] ์ฑ„๋ฒˆ API ํ˜ธ์ถœ ์‹คํŒจ:", error); + } + } + console.log("[RepeatScreenModal] ์ƒˆ ํ–‰ ์ถ”๊ฐ€:", { cardId, contentRowId, @@ -1329,8 +1468,13 @@ export function RepeatScreenModalComponent({ for (const fn of extAggFunctions) { const regex = new RegExp(`${fn}\\(\\{(\\w+)\\}\\)`, "g"); expression = expression.replace(regex, (match, fieldName) => { - if (!externalRows || externalRows.length === 0) return "0"; + if (!externalRows || externalRows.length === 0) { + console.log(`[SUM_EXT] ${fieldName}: ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์—†์Œ`); + return "0"; + } const values = externalRows.map((row) => Number(row[fieldName]) || 0); + const sum = values.reduce((a, b) => a + b, 0); + console.log(`[SUM_EXT] ${fieldName}: ${externalRows.length}๊ฐœ ํ–‰, ๊ฐ’๋“ค:`, values, `ํ•ฉ๊ณ„: ${sum}`); const baseFn = fn.replace("_EXT", ""); switch (baseFn) { case "SUM": @@ -1531,6 +1675,9 @@ export function RepeatScreenModalComponent({ // ๐Ÿ†• v3.1: ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์ €์žฅ await saveExternalTableData(); + // ๐Ÿ†• v3.12: ์—ฐ๋™ ์ €์žฅ ์ฒ˜๋ฆฌ (syncSaves) + await processSyncSaves(); + alert("์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); } catch (error: any) { console.error("์ €์žฅ ์‹คํŒจ:", error); @@ -1588,6 +1735,102 @@ export function RepeatScreenModalComponent({ }); }; + // ๐Ÿ†• v3.12: ์—ฐ๋™ ์ €์žฅ ์ฒ˜๋ฆฌ (syncSaves) + const processSyncSaves = async () => { + const syncPromises: Promise[] = []; + + // contentRows์—์„œ syncSaves๊ฐ€ ์„ค์ •๋œ ํ…Œ์ด๋ธ” ํ–‰ ์ฐพ๊ธฐ + for (const contentRow of contentRows) { + if (contentRow.type !== "table") continue; + if (!contentRow.tableCrud?.syncSaves?.length) continue; + + const sourceTable = contentRow.tableDataSource?.sourceTable; + if (!sourceTable) continue; + + // ์ด ํ…Œ์ด๋ธ” ํ–‰์˜ ๋ชจ๋“  ์นด๋“œ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ + for (const card of groupedCardsData) { + const key = `${card._cardId}-${contentRow.id}`; + const rows = (externalTableData[key] || []).filter((row) => !row._isDeleted); + + // ๊ฐ syncSave ์„ค์ • ์ฒ˜๋ฆฌ + for (const syncSave of contentRow.tableCrud.syncSaves) { + if (!syncSave.enabled) continue; + if (!syncSave.sourceColumn || !syncSave.targetTable || !syncSave.targetColumn) continue; + + // ์กฐ์ธ ํ‚ค ๊ฐ’ ์ˆ˜์ง‘ (์ค‘๋ณต ์ œ๊ฑฐ) + const joinKeyValues = new Set(); + for (const row of rows) { + const keyValue = row[syncSave.joinKey.sourceField]; + if (keyValue !== undefined && keyValue !== null) { + joinKeyValues.add(keyValue); + } + } + + // ๊ฐ ์กฐ์ธ ํ‚ค๋ณ„๋กœ ์ง‘๊ณ„ ๊ณ„์‚ฐ ๋ฐ ์—…๋ฐ์ดํŠธ + for (const keyValue of joinKeyValues) { + // ํ•ด๋‹น ์กฐ์ธ ํ‚ค์— ํ•ด๋‹นํ•˜๋Š” ํ–‰๋“ค๋งŒ ํ•„ํ„ฐ๋ง + const filteredRows = rows.filter( + (row) => row[syncSave.joinKey.sourceField] === keyValue + ); + + // ์ง‘๊ณ„ ๊ณ„์‚ฐ + let aggregatedValue: number = 0; + const values = filteredRows.map((row) => Number(row[syncSave.sourceColumn]) || 0); + + switch (syncSave.aggregationType) { + case "sum": + aggregatedValue = values.reduce((a, b) => a + b, 0); + break; + case "count": + aggregatedValue = values.length; + break; + case "avg": + aggregatedValue = values.length > 0 ? values.reduce((a, b) => a + b, 0) / values.length : 0; + break; + case "min": + aggregatedValue = values.length > 0 ? Math.min(...values) : 0; + break; + case "max": + aggregatedValue = values.length > 0 ? Math.max(...values) : 0; + break; + case "latest": + aggregatedValue = values.length > 0 ? values[values.length - 1] : 0; + break; + } + + console.log(`[SyncSave] ${sourceTable}.${syncSave.sourceColumn} โ†’ ${syncSave.targetTable}.${syncSave.targetColumn}`, { + joinKey: keyValue, + aggregationType: syncSave.aggregationType, + values, + aggregatedValue, + }); + + // ๋Œ€์ƒ ํ…Œ์ด๋ธ” ์—…๋ฐ์ดํŠธ + syncPromises.push( + apiClient + .put(`/table-management/tables/${syncSave.targetTable}/data/${keyValue}`, { + [syncSave.targetColumn]: aggregatedValue, + }) + .then(() => { + console.log(`[SyncSave] ์—…๋ฐ์ดํŠธ ์™„๋ฃŒ: ${syncSave.targetTable}.${syncSave.targetColumn} = ${aggregatedValue} (id=${keyValue})`); + }) + .catch((err) => { + console.error(`[SyncSave] ์—…๋ฐ์ดํŠธ ์‹คํŒจ:`, err); + throw err; + }) + ); + } + } + } + } + + if (syncPromises.length > 0) { + console.log(`[SyncSave] ${syncPromises.length}๊ฐœ ์—ฐ๋™ ์ €์žฅ ์ฒ˜๋ฆฌ ์ค‘...`); + await Promise.all(syncPromises); + console.log(`[SyncSave] ์—ฐ๋™ ์ €์žฅ ์™„๋ฃŒ`); + } + }; + // ๐Ÿ†• v3.1: Footer ๋ฒ„ํŠผ ํด๋ฆญ ํ•ธ๋“ค๋Ÿฌ const handleFooterButtonClick = async (btn: FooterButtonConfig) => { switch (btn.action) { @@ -1934,27 +2177,10 @@ export function RepeatScreenModalComponent({ // ๐Ÿ†• v3.1: ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์†Œ์Šค ์‚ฌ์šฉ
{/* ํ…Œ์ด๋ธ” ํ—ค๋” ์˜์—ญ: ์ œ๋ชฉ + ๋ฒ„ํŠผ๋“ค */} - {(contentRow.tableTitle || contentRow.tableCrud?.allowSave || contentRow.tableCrud?.allowCreate) && ( + {(contentRow.tableTitle || contentRow.tableCrud?.allowCreate) && (
{contentRow.tableTitle || ""}
- {/* ์ €์žฅ ๋ฒ„ํŠผ - allowSave๊ฐ€ true์ผ ๋•Œ๋งŒ ํ‘œ์‹œ */} - {contentRow.tableCrud?.allowSave && ( - - )} {/* ์ถ”๊ฐ€ ๋ฒ„ํŠผ */} {contentRow.tableCrud?.allowCreate && (
+ + {/* ๐Ÿ†• v3.11: SUM_EXT ์ฐธ์กฐ ํ…Œ์ด๋ธ” ์„ ํƒ */} + {localFormula.includes("_EXT") && ( + onUpdate({ externalTableRefs: refs })} + /> + )}
)} @@ -1280,6 +1296,504 @@ function FormulaColumnAggregator({ ); } +// ๐Ÿ†• v3.11: SUM_EXT ์ฐธ์กฐ ํ…Œ์ด๋ธ” ์„ ํƒ ์ปดํฌ๋„ŒํŠธ +function ExternalTableRefSelector({ + contentRows, + selectedRefs, + onUpdate, +}: { + contentRows: CardContentRowConfig[]; + selectedRefs: string[]; + onUpdate: (refs: string[]) => void; +}) { + // ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์†Œ์Šค๊ฐ€ ํ™œ์„ฑํ™”๋œ ํ…Œ์ด๋ธ” ํ–‰๋งŒ ํ•„ํ„ฐ๋ง + const tableRowsWithExternalSource = contentRows.filter( + (row) => row.type === "table" && row.tableDataSource?.enabled + ); + + if (tableRowsWithExternalSource.length === 0) { + return ( +
+

+ ๋ ˆ์ด์•„์›ƒ์— ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์†Œ์Šค๊ฐ€ ์„ค์ •๋œ ํ…Œ์ด๋ธ” ํ–‰์ด ์—†์Šต๋‹ˆ๋‹ค. +

+
+ ); + } + + const isAllSelected = selectedRefs.length === 0; + + const handleToggleTable = (tableId: string) => { + if (selectedRefs.includes(tableId)) { + // ์ด๋ฏธ ์„ ํƒ๋œ ๊ฒฝ์šฐ ์ œ๊ฑฐ + const newRefs = selectedRefs.filter((id) => id !== tableId); + onUpdate(newRefs); + } else { + // ์„ ํƒ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์ถ”๊ฐ€ + onUpdate([...selectedRefs, tableId]); + } + }; + + const handleSelectAll = () => { + onUpdate([]); // ๋นˆ ๋ฐฐ์—ด = ๋ชจ๋“  ํ…Œ์ด๋ธ” ์‚ฌ์šฉ + }; + + return ( +
+
+ + +
+ +

+ SUM_EXT ํ•จ์ˆ˜๊ฐ€ ์ฐธ์กฐํ•  ํ…Œ์ด๋ธ”์„ ์„ ํƒํ•˜์„ธ์š”. ์„ ํƒํ•˜์ง€ ์•Š์œผ๋ฉด ๋ชจ๋“  ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +

+ +
+ {tableRowsWithExternalSource.map((row) => { + const isSelected = selectedRefs.length === 0 || selectedRefs.includes(row.id); + const tableTitle = row.title || row.tableDataSource?.sourceTable || row.id; + const tableName = row.tableDataSource?.sourceTable || ""; + + return ( +
handleToggleTable(row.id)} + > + {}} // onClick์—์„œ ์ฒ˜๋ฆฌ + className="h-4 w-4 rounded border-gray-300 text-amber-600 focus:ring-amber-500" + /> +
+

{tableTitle}

+

+ ํ…Œ์ด๋ธ”: {tableName} | ID: {row.id.slice(-10)} +

+
+
+ ); + })} +
+ + {selectedRefs.length > 0 && ( +

+ ์„ ํƒ๋œ ํ…Œ์ด๋ธ”: {selectedRefs.length}๊ฐœ (ํŠน์ • ํ…Œ์ด๋ธ”๋งŒ ์ฐธ์กฐ) +

+ )} +
+ ); +} + +// ๐Ÿ†• v3.12: ์—ฐ๋™ ์ €์žฅ ์„ค์ • ์„น์…˜ +function SyncSaveConfigSection({ + row, + allTables, + onUpdateRow, +}: { + row: CardContentRowConfig; + allTables: { tableName: string; displayName?: string }[]; + onUpdateRow: (updates: Partial) => void; +}) { + const syncSaves = row.tableCrud?.syncSaves || []; + const sourceTable = row.tableDataSource?.sourceTable || ""; + + // ์—ฐ๋™ ์ €์žฅ ์ถ”๊ฐ€ + const addSyncSave = () => { + const newSyncSave: SyncSaveConfig = { + id: `sync-${Date.now()}`, + enabled: true, + sourceColumn: "", + aggregationType: "sum", + targetTable: "", + targetColumn: "", + joinKey: { + sourceField: "", + targetField: "id", + }, + }; + + onUpdateRow({ + tableCrud: { + ...row.tableCrud, + allowCreate: row.tableCrud?.allowCreate || false, + allowUpdate: row.tableCrud?.allowUpdate || false, + allowDelete: row.tableCrud?.allowDelete || false, + syncSaves: [...syncSaves, newSyncSave], + }, + }); + }; + + // ์—ฐ๋™ ์ €์žฅ ์‚ญ์ œ + const removeSyncSave = (index: number) => { + const newSyncSaves = [...syncSaves]; + newSyncSaves.splice(index, 1); + + onUpdateRow({ + tableCrud: { + ...row.tableCrud, + allowCreate: row.tableCrud?.allowCreate || false, + allowUpdate: row.tableCrud?.allowUpdate || false, + allowDelete: row.tableCrud?.allowDelete || false, + syncSaves: newSyncSaves, + }, + }); + }; + + // ์—ฐ๋™ ์ €์žฅ ์—…๋ฐ์ดํŠธ + const updateSyncSave = (index: number, updates: Partial) => { + const newSyncSaves = [...syncSaves]; + newSyncSaves[index] = { ...newSyncSaves[index], ...updates }; + + onUpdateRow({ + tableCrud: { + ...row.tableCrud, + allowCreate: row.tableCrud?.allowCreate || false, + allowUpdate: row.tableCrud?.allowUpdate || false, + allowDelete: row.tableCrud?.allowDelete || false, + syncSaves: newSyncSaves, + }, + }); + }; + + return ( +
+
+ + +
+ + {syncSaves.length === 0 ? ( +

+ ์—ฐ๋™ ์ €์žฅ ์„ค์ •์ด ์—†์Šต๋‹ˆ๋‹ค. ์ถ”๊ฐ€ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ์„ค์ •ํ•˜์„ธ์š”. +

+ ) : ( +
+ {syncSaves.map((sync, index) => ( + updateSyncSave(index, updates)} + onRemove={() => removeSyncSave(index)} + /> + ))} +
+ )} +
+ ); +} + +// ๐Ÿ†• v3.12: ๊ฐœ๋ณ„ ์—ฐ๋™ ์ €์žฅ ์„ค์ • ์•„์ดํ…œ +function SyncSaveConfigItem({ + sync, + index, + sourceTable, + allTables, + onUpdate, + onRemove, +}: { + sync: SyncSaveConfig; + index: number; + sourceTable: string; + allTables: { tableName: string; displayName?: string }[]; + onUpdate: (updates: Partial) => void; + onRemove: () => void; +}) { + return ( +
+ {/* ํ—ค๋” */} +
+
+ onUpdate({ enabled: checked })} + className="scale-[0.6]" + /> + + ์—ฐ๋™ {index + 1} + +
+ +
+ + {/* ์†Œ์Šค ์„ค์ • */} +
+
+ + onUpdate({ sourceColumn: value })} + placeholder="์ปฌ๋Ÿผ ์„ ํƒ" + /> +
+
+ + +
+
+ + {/* ๋Œ€์ƒ ์„ค์ • */} +
+
+ + +
+
+ + onUpdate({ targetColumn: value })} + placeholder="์ปฌ๋Ÿผ ์„ ํƒ" + /> +
+
+ + {/* ์กฐ์ธ ํ‚ค ์„ค์ • */} +
+
+ + onUpdate({ joinKey: { ...sync.joinKey, sourceField: value } })} + placeholder="์˜ˆ: sales_order_id" + /> +
+
+ + onUpdate({ joinKey: { ...sync.joinKey, targetField: value } })} + placeholder="์˜ˆ: id" + /> +
+
+ + {/* ์„ค์ • ์š”์•ฝ */} + {sync.sourceColumn && sync.targetTable && sync.targetColumn && ( +

+ {sourceTable}.{sync.sourceColumn}์˜ {sync.aggregationType.toUpperCase()} ๊ฐ’์„{" "} + {sync.targetTable}.{sync.targetColumn}์— ์ €์žฅ +

+ )} +
+ ); +} + +// ๐Ÿ†• v3.13: ํ–‰ ์ถ”๊ฐ€ ์‹œ ์ž๋™ ์ฑ„๋ฒˆ ์„ค์ • ์„น์…˜ +function RowNumberingConfigSection({ + row, + onUpdateRow, +}: { + row: CardContentRowConfig; + onUpdateRow: (updates: Partial) => void; +}) { + const [numberingRules, setNumberingRules] = useState<{ id: string; name: string; code: string }[]>([]); + const [isLoading, setIsLoading] = useState(false); + + const rowNumbering = row.tableCrud?.rowNumbering; + const tableColumns = row.tableColumns || []; + + // ์ฑ„๋ฒˆ ๊ทœ์น™ ๋ชฉ๋ก ๋กœ๋“œ (์˜ต์…˜์„ค์ • > ์ฝ”๋“œ์„ค์ •์—์„œ ๋“ฑ๋ก๋œ ์ „์ฒด ๋ชฉ๋ก) + useEffect(() => { + const loadNumberingRules = async () => { + setIsLoading(true); + try { + const { getNumberingRules } = await import("@/lib/api/numberingRule"); + const response = await getNumberingRules(); + if (response.success && response.data) { + setNumberingRules(response.data.map((rule: any, index: number) => ({ + id: String(rule.ruleId || rule.id || `rule-${index}`), + name: rule.ruleName || rule.name || "์ด๋ฆ„ ์—†์Œ", + code: rule.ruleId || rule.code || "", + }))); + } + } catch (error) { + console.error("์ฑ„๋ฒˆ ๊ทœ์น™ ๋กœ๋“œ ์‹คํŒจ:", error); + setNumberingRules([]); + } finally { + setIsLoading(false); + } + }; + loadNumberingRules(); + }, []); + + // ์ฑ„๋ฒˆ ์„ค์ • ์—…๋ฐ์ดํŠธ + const updateRowNumbering = (updates: Partial) => { + const currentNumbering = row.tableCrud?.rowNumbering || { + enabled: false, + targetColumn: "", + numberingRuleId: "", + }; + + onUpdateRow({ + tableCrud: { + ...row.tableCrud, + allowCreate: row.tableCrud?.allowCreate || false, + allowUpdate: row.tableCrud?.allowUpdate || false, + allowDelete: row.tableCrud?.allowDelete || false, + rowNumbering: { + ...currentNumbering, + ...updates, + }, + }, + }); + }; + + return ( +
+
+
+ updateRowNumbering({ enabled: checked })} + className="scale-90" + /> + +
+
+ +

+ "์ถ”๊ฐ€" ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์ง€์ •ํ•œ ์ปฌ๋Ÿผ์— ์ž๋™์œผ๋กœ ๋ฒˆํ˜ธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + (์˜ต์…˜์„ค์ • > ์ฝ”๋“œ์„ค์ •์—์„œ ๋“ฑ๋กํ•œ ์ฑ„๋ฒˆ ๊ทœ์น™ ์‚ฌ์šฉ) +

+ + {rowNumbering?.enabled && ( +
+ {/* ๋Œ€์ƒ ์ปฌ๋Ÿผ ์„ ํƒ */} +
+ + +

+ ์ฑ„๋ฒˆ ๊ฒฐ๊ณผ๊ฐ€ ์ €์žฅ๋  ์ปฌ๋Ÿผ (์ˆ˜์ • ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋Š” ์ปฌ๋Ÿผ ์„ค์ •์—์„œ ์กฐ์ ˆ) +

+
+ + {/* ์ฑ„๋ฒˆ ๊ทœ์น™ ์„ ํƒ */} +
+ + + {numberingRules.length === 0 && !isLoading && ( +

+ ๋“ฑ๋ก๋œ ์ฑ„๋ฒˆ ๊ทœ์น™์ด ์—†์Šต๋‹ˆ๋‹ค. ์˜ต์…˜์„ค์ • > ์ฝ”๋“œ์„ค์ •์—์„œ ์ถ”๊ฐ€ํ•˜์„ธ์š”. +

+ )} +
+ + {/* ์„ค์ • ์š”์•ฝ */} + {rowNumbering.targetColumn && rowNumbering.numberingRuleId && ( +
+ "์ถ”๊ฐ€" ํด๋ฆญ ์‹œ {rowNumbering.targetColumn} ์ปฌ๋Ÿผ์— ์ž๋™ ์ฑ„๋ฒˆ +
+ )} +
+ )} +
+ ); +} + // ๐Ÿ†• ๋ ˆ์ด์•„์›ƒ ์„ค์ • ์ „์šฉ ๋ชจ๋‹ฌ function LayoutSettingsModal({ open, @@ -2040,6 +2554,78 @@ function LayoutRowConfigModal({ )}
+ {/* CRUD ์„ค์ • */} +
+ +
+
+ + onUpdateRow({ + tableCrud: { ...row.tableCrud, allowCreate: checked, allowUpdate: row.tableCrud?.allowUpdate || false, allowDelete: row.tableCrud?.allowDelete || false }, + }) + } + className="scale-90" + /> + +
+
+ + onUpdateRow({ + tableCrud: { ...row.tableCrud, allowCreate: row.tableCrud?.allowCreate || false, allowUpdate: checked, allowDelete: row.tableCrud?.allowDelete || false }, + }) + } + className="scale-90" + /> + +
+
+ + onUpdateRow({ + tableCrud: { ...row.tableCrud, allowCreate: row.tableCrud?.allowCreate || false, allowUpdate: row.tableCrud?.allowUpdate || false, allowDelete: checked }, + }) + } + className="scale-90" + /> + +
+
+ {row.tableCrud?.allowDelete && ( +
+ + onUpdateRow({ + tableCrud: { ...row.tableCrud!, deleteConfirm: { enabled: checked } }, + }) + } + className="scale-75" + /> + +
+ )} +
+ + {/* ๐Ÿ†• v3.13: ํ–‰ ์ถ”๊ฐ€ ์‹œ ์ž๋™ ์ฑ„๋ฒˆ ์„ค์ • */} + {row.tableCrud?.allowCreate && ( + + )} + + {/* ๐Ÿ†• v3.12: ์—ฐ๋™ ์ €์žฅ ์„ค์ • */} + + {/* ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋ชฉ๋ก */}
@@ -2077,7 +2663,7 @@ function LayoutRowConfigModal({
-
+
{col.editable ? "์˜ˆ" : "์•„๋‹ˆ์˜ค"}
+
+ +
+ onUpdateTableColumn(colIndex, { hidden: checked })} + className="scale-75" + /> + {col.hidden ? "์˜ˆ" : "์•„๋‹ˆ์˜ค"} +
+
))} @@ -3188,21 +3785,21 @@ export function RepeatScreenModalConfigPanel({ config, onChange }: RepeatScreenM
- +
{/* ํ˜„์žฌ ์ง‘๊ณ„ ๋ชฉ๋ก ์š”์•ฝ */} {(localConfig.grouping?.aggregations || []).length > 0 ? (
- {(localConfig.grouping?.aggregations || []).map((agg, index) => ( + {(localConfig.grouping?.aggregations || []).map((agg, index) => (

๋ ˆ์ด์•„์›ƒ ํ–‰

- +
{/* ํ˜„์žฌ ๋ ˆ์ด์•„์›ƒ ์š”์•ฝ */} @@ -3324,6 +3921,7 @@ export function RepeatScreenModalConfigPanel({ config, onChange }: RepeatScreenM aggregations={localConfig.grouping?.aggregations || []} sourceTable={localConfig.dataSource?.sourceTable || ""} allTables={allTables} + contentRows={localConfig.contentRows || []} onSave={(newAggregations) => { updateGrouping({ aggregations: newAggregations }); }} @@ -4192,7 +4790,7 @@ function ContentRowConfigSection({ checked={row.tableCrud?.allowCreate || false} onCheckedChange={(checked) => onUpdateRow({ - tableCrud: { ...row.tableCrud, allowCreate: checked, allowUpdate: row.tableCrud?.allowUpdate || false, allowDelete: row.tableCrud?.allowDelete || false, allowSave: row.tableCrud?.allowSave || false }, + tableCrud: { ...row.tableCrud, allowCreate: checked, allowUpdate: row.tableCrud?.allowUpdate || false, allowDelete: row.tableCrud?.allowDelete || false }, }) } className="scale-[0.5]" @@ -4204,7 +4802,7 @@ function ContentRowConfigSection({ checked={row.tableCrud?.allowUpdate || false} onCheckedChange={(checked) => onUpdateRow({ - tableCrud: { ...row.tableCrud, allowCreate: row.tableCrud?.allowCreate || false, allowUpdate: checked, allowDelete: row.tableCrud?.allowDelete || false, allowSave: row.tableCrud?.allowSave || false }, + tableCrud: { ...row.tableCrud, allowCreate: row.tableCrud?.allowCreate || false, allowUpdate: checked, allowDelete: row.tableCrud?.allowDelete || false }, }) } className="scale-[0.5]" @@ -4216,25 +4814,13 @@ function ContentRowConfigSection({ checked={row.tableCrud?.allowDelete || false} onCheckedChange={(checked) => onUpdateRow({ - tableCrud: { ...row.tableCrud, allowCreate: row.tableCrud?.allowCreate || false, allowUpdate: row.tableCrud?.allowUpdate || false, allowDelete: checked, allowSave: row.tableCrud?.allowSave || false }, + tableCrud: { ...row.tableCrud, allowCreate: row.tableCrud?.allowCreate || false, allowUpdate: row.tableCrud?.allowUpdate || false, allowDelete: checked }, }) } className="scale-[0.5]" />
-
- - onUpdateRow({ - tableCrud: { ...row.tableCrud, allowCreate: row.tableCrud?.allowCreate || false, allowUpdate: row.tableCrud?.allowUpdate || false, allowDelete: row.tableCrud?.allowDelete || false, allowSave: checked }, - }) - } - className="scale-[0.5]" - /> - -
{row.tableCrud?.allowDelete && (
@@ -4252,6 +4838,21 @@ function ContentRowConfigSection({ )}
+ {/* ๐Ÿ†• v3.12: ์—ฐ๋™ ์ €์žฅ ์„ค์ • */} + + + {/* ๐Ÿ†• v3.13: ํ–‰ ์ถ”๊ฐ€ ์‹œ ์ž๋™ ์ฑ„๋ฒˆ ์„ค์ • */} + {row.tableCrud?.allowCreate && ( + + )} + {/* ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋ชฉ๋ก */}
diff --git a/frontend/lib/registry/components/repeat-screen-modal/types.ts b/frontend/lib/registry/components/repeat-screen-modal/types.ts index 47242d5a..92def760 100644 --- a/frontend/lib/registry/components/repeat-screen-modal/types.ts +++ b/frontend/lib/registry/components/repeat-screen-modal/types.ts @@ -188,10 +188,6 @@ export interface TableCrudConfig { allowUpdate: boolean; // ํ–‰ ์ˆ˜์ • ํ—ˆ์šฉ allowDelete: boolean; // ํ–‰ ์‚ญ์ œ ํ—ˆ์šฉ - // ๐Ÿ†• v3.5: ํ…Œ์ด๋ธ” ์˜์—ญ ์ €์žฅ ๋ฒ„ํŠผ - allowSave?: boolean; // ํ…Œ์ด๋ธ” ์˜์—ญ์— ์ €์žฅ ๋ฒ„ํŠผ ํ‘œ์‹œ - saveButtonLabel?: string; // ์ €์žฅ ๋ฒ„ํŠผ ๋ผ๋ฒจ (๊ธฐ๋ณธ: "์ €์žฅ") - // ์‹ ๊ทœ ํ–‰ ๊ธฐ๋ณธ๊ฐ’ newRowDefaults?: Record; // ๊ธฐ๋ณธ๊ฐ’ (์˜ˆ: { status: "READY", sales_order_id: "{id}" }) @@ -203,6 +199,54 @@ export interface TableCrudConfig { // ์ €์žฅ ๋Œ€์ƒ ํ…Œ์ด๋ธ” (์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์†Œ์Šค ์‚ฌ์šฉ ์‹œ) targetTable?: string; // ์ €์žฅํ•  ํ…Œ์ด๋ธ” (๊ธฐ๋ณธ: tableDataSource.sourceTable) + + // ๐Ÿ†• v3.12: ์—ฐ๋™ ์ €์žฅ ์„ค์ • (๋ชจ๋‹ฌ ์ „์ฒด ์ €์žฅ ์‹œ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์—๋„ ๋™๊ธฐํ™”) + syncSaves?: SyncSaveConfig[]; + + // ๐Ÿ†• v3.13: ํ–‰ ์ถ”๊ฐ€ ์‹œ ์ž๋™ ์ฑ„๋ฒˆ ์„ค์ • + rowNumbering?: RowNumberingConfig; +} + +/** + * ๐Ÿ†• v3.13: ํ…Œ์ด๋ธ” ํ–‰ ์ฑ„๋ฒˆ ์„ค์ • + * "์ถ”๊ฐ€" ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ํŠน์ • ์ปฌ๋Ÿผ์— ์ž๋™์œผ๋กœ ๋ฒˆํ˜ธ๋ฅผ ์ƒ์„ฑ + * + * ์‚ฌ์šฉ ์˜ˆ์‹œ: + * - ์ถœํ•˜๊ณ„ํš๋ฒˆํ˜ธ(shipment_plan_no) ์ž๋™ ์ƒ์„ฑ + * - ์†ก์žฅ๋ฒˆํ˜ธ(invoice_no) ์ž๋™ ์ƒ์„ฑ + * - ์ž‘์—…์ง€์‹œ๋ฒˆํ˜ธ(work_order_no) ์ž๋™ ์ƒ์„ฑ + * + * ์ฐธ๊ณ : ์ฑ„๋ฒˆ ํ›„ ์ฝ๊ธฐ ์ „์šฉ ์—ฌ๋ถ€๋Š” ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ์˜ "์ˆ˜์ • ๊ฐ€๋Šฅ" ์„ค์ •์œผ๋กœ ์ œ์–ด + */ +export interface RowNumberingConfig { + enabled: boolean; // ์ฑ„๋ฒˆ ์‚ฌ์šฉ ์—ฌ๋ถ€ + targetColumn: string; // ์ฑ„๋ฒˆ ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•  ์ปฌ๋Ÿผ (์˜ˆ: "shipment_plan_no") + + // ์ฑ„๋ฒˆ ๊ทœ์น™ ์„ค์ • (์˜ต์…˜์„ค์ • > ์ฝ”๋“œ์„ค์ •์—์„œ ๋“ฑ๋ก๋œ ์ฑ„๋ฒˆ ๊ทœ์น™) + numberingRuleId: string; // ์ฑ„๋ฒˆ ๊ทœ์น™ ID (numbering_rule ํ…Œ์ด๋ธ”) +} + +/** + * ๐Ÿ†• v3.12: ์—ฐ๋™ ์ €์žฅ ์„ค์ • + * ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์ €์žฅ ์‹œ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์˜ ํŠน์ • ์ปฌ๋Ÿผ์— ์ง‘๊ณ„ ๊ฐ’์„ ๋™๊ธฐํ™” + */ +export interface SyncSaveConfig { + id: string; // ๊ณ ์œ  ID + enabled: boolean; // ํ™œ์„ฑํ™” ์—ฌ๋ถ€ + + // ์†Œ์Šค ์„ค์ • (์ด ํ…Œ์ด๋ธ”์—์„œ) + sourceColumn: string; // ์ง‘๊ณ„ํ•  ์ปฌ๋Ÿผ (์˜ˆ: "plan_qty") + aggregationType: "sum" | "count" | "avg" | "min" | "max" | "latest"; // ์ง‘๊ณ„ ๋ฐฉ์‹ + + // ๋Œ€์ƒ ์„ค์ • (์ €์žฅํ•  ํ…Œ์ด๋ธ”) + targetTable: string; // ๋Œ€์ƒ ํ…Œ์ด๋ธ” (์˜ˆ: "sales_order_mng") + targetColumn: string; // ๋Œ€์ƒ ์ปฌ๋Ÿผ (์˜ˆ: "plan_ship_qty") + + // ์กฐ์ธ ํ‚ค (์–ด๋–ค ๋ ˆ์ฝ”๋“œ๋ฅผ ์—…๋ฐ์ดํŠธํ• ์ง€) + joinKey: { + sourceField: string; // ์ด ํ…Œ์ด๋ธ”์˜ ์กฐ์ธ ํ‚ค (์˜ˆ: "sales_order_id") + targetField: string; // ๋Œ€์ƒ ํ…Œ์ด๋ธ”์˜ ํ‚ค (์˜ˆ: "id") + }; } /** @@ -285,6 +329,12 @@ export interface AggregationConfig { // - ์‚ฐ์ˆ  ์—ฐ์‚ฐ: +, -, *, /, () formula?: string; // ์—ฐ์‚ฐ์‹ (์˜ˆ: "{total_balance} - SUM_EXT({plan_qty})") + // === ๐Ÿ†• v3.11: SUM_EXT ์ฐธ์กฐ ํ…Œ์ด๋ธ” ์ œํ•œ === + // SUM_EXT ํ•จ์ˆ˜๊ฐ€ ์ฐธ์กฐํ•  ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ํ–‰ ID ๋ชฉ๋ก + // ๋น„์–ด์žˆ๊ฑฐ๋‚˜ undefined๋ฉด ๋ชจ๋“  ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ (๊ธฐ์กด ๋™์ž‘) + // ํŠน์ • ํ…Œ์ด๋ธ”๋งŒ ์ฐธ์กฐํ•˜๋ ค๋ฉด contentRow์˜ id๋ฅผ ๋ฐฐ์—ด๋กœ ์ง€์ • + externalTableRefs?: string[]; // ์ฐธ์กฐํ•  ํ…Œ์ด๋ธ” ํ–‰ ID ๋ชฉ๋ก (์˜ˆ: ["crow-1764571929625"]) + // === ๊ณตํ†ต === resultField: string; // ๊ฒฐ๊ณผ ํ•„๋“œ๋ช… (์˜ˆ: "total_balance_qty") label: string; // ํ‘œ์‹œ ๋ผ๋ฒจ (์˜ˆ: "์ด์ˆ˜์ฃผ์ž”๋Ÿ‰") @@ -340,6 +390,9 @@ export interface TableColumnConfig { editable: boolean; // ํŽธ์ง‘ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ required?: boolean; // ํ•„์ˆ˜ ์ž…๋ ฅ ์—ฌ๋ถ€ + // ๐Ÿ†• v3.13: ์ˆจ๊น€ ์„ค์ • (ํ™”๋ฉด์—๋Š” ์•ˆ ๋ณด์ด์ง€๋งŒ ๋ฐ์ดํ„ฐ๋Š” ์กด์žฌ) + hidden?: boolean; // ์ˆจ๊น€ ์—ฌ๋ถ€ + // ๐Ÿ†• v3.3: ์ปฌ๋Ÿผ ์†Œ์Šค ํ…Œ์ด๋ธ” ์ง€์ • (์กฐ์ธ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ์‚ฌ์šฉ ์‹œ) fromTable?: string; // ์ปฌ๋Ÿผ์ด ์†ํ•œ ํ…Œ์ด๋ธ” (๋น„์–ด์žˆ์œผ๋ฉด ์†Œ์Šค ํ…Œ์ด๋ธ”) fromJoinId?: string; // additionalJoins์˜ id ์ฐธ์กฐ (์กฐ์ธ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ์ผ ๋•Œ) diff --git a/frontend/lib/registry/components/text-input/TextInputComponent.tsx b/frontend/lib/registry/components/text-input/TextInputComponent.tsx index f97609a6..72dabb61 100644 --- a/frontend/lib/registry/components/text-input/TextInputComponent.tsx +++ b/frontend/lib/registry/components/text-input/TextInputComponent.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react"; import { ComponentRendererProps } from "@/types/component"; -import { AutoGenerationConfig } from "@/types/screen"; +import { AutoGenerationConfig, AutoGenerationType } from "@/types/screen"; import { TextInputConfig } from "./types"; import { filterDOMProps } from "@/lib/utils/domPropsFilter"; import { AutoGenerationUtils } from "@/lib/utils/autoGeneration"; diff --git a/frontend/lib/registry/components/text-input/TextInputConfigPanel.tsx b/frontend/lib/registry/components/text-input/TextInputConfigPanel.tsx index 69088e96..5dd67812 100644 --- a/frontend/lib/registry/components/text-input/TextInputConfigPanel.tsx +++ b/frontend/lib/registry/components/text-input/TextInputConfigPanel.tsx @@ -5,6 +5,8 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Button } from "@/components/ui/button"; +import { Plus, Trash2 } from "lucide-react"; import { TextInputConfig } from "./types"; import { AutoGenerationType, AutoGenerationConfig } from "@/types/screen"; import { AutoGenerationUtils } from "@/lib/utils/autoGeneration";