diff --git a/backend-node/src/controllers/tableManagementController.ts b/backend-node/src/controllers/tableManagementController.ts index f552124f..4a80b007 100644 --- a/backend-node/src/controllers/tableManagementController.ts +++ b/backend-node/src/controllers/tableManagementController.ts @@ -870,6 +870,17 @@ export async function addTableData( const tableManagementService = new TableManagementService(); + // ๐Ÿ†• ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ: company_code ์ž๋™ ์ถ”๊ฐ€ (ํ…Œ์ด๋ธ”์— company_code ์ปฌ๋Ÿผ์ด ์žˆ๋Š” ๊ฒฝ์šฐ) + const companyCode = req.user?.companyCode; + if (companyCode && !data.company_code) { + // ํ…Œ์ด๋ธ”์— company_code ์ปฌ๋Ÿผ์ด ์žˆ๋Š”์ง€ ํ™•์ธ + const hasCompanyCodeColumn = await tableManagementService.hasColumn(tableName, "company_code"); + if (hasCompanyCodeColumn) { + data.company_code = companyCode; + logger.info(`๐Ÿ”’ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ: company_code ์ž๋™ ์ถ”๊ฐ€ - ${companyCode}`); + } + } + // ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ await tableManagementService.addTableData(tableName, data); diff --git a/backend-node/src/services/tableManagementService.ts b/backend-node/src/services/tableManagementService.ts index c1748123..dabe41da 100644 --- a/backend-node/src/services/tableManagementService.ts +++ b/backend-node/src/services/tableManagementService.ts @@ -4076,4 +4076,22 @@ export class TableManagementService { throw error; } } + + /** + * ํ…Œ์ด๋ธ”์— ํŠน์ • ์ปฌ๋Ÿผ์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ + */ + async hasColumn(tableName: string, columnName: string): Promise { + try { + const result = await query( + `SELECT column_name + FROM information_schema.columns + WHERE table_name = $1 AND column_name = $2`, + [tableName, columnName] + ); + return result.length > 0; + } catch (error) { + logger.error(`์ปฌ๋Ÿผ ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ ์‹คํŒจ: ${tableName}.${columnName}`, error); + return false; + } + } } diff --git a/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx b/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx index 25807607..2484f1d7 100644 --- a/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx +++ b/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx @@ -295,6 +295,8 @@ export function RepeatScreenModalComponent({ rowCount: tableData.length, sampleRow: tableData[0] ? Object.keys(tableData[0]) : [], firstRowData: tableData[0], + // ๋””๋ฒ„๊ทธ: plan_date ํ•„๋“œ ํ™•์ธ + plan_date_value: tableData[0]?.plan_date, }); // ๐Ÿ†• v3.3: ์ถ”๊ฐ€ ์กฐ์ธ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ๋กœ๋“œ ๋ฐ ๋ณ‘ํ•ฉ @@ -344,6 +346,14 @@ export function RepeatScreenModalComponent({ _isNew: false, ...row, })); + + // ๋””๋ฒ„๊ทธ: ์ €์žฅ๋œ ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ํ™•์ธ + console.log(`[RepeatScreenModal] ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์ €์žฅ:`, { + key, + rowCount: newExternalData[key].length, + firstRow: newExternalData[key][0], + plan_date_in_firstRow: newExternalData[key][0]?.plan_date, + }); } } catch (error) { console.error(`[RepeatScreenModal] ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ:`, error); @@ -599,6 +609,159 @@ export function RepeatScreenModalComponent({ }); }; + // ๐Ÿ†• v3.6: ํ…Œ์ด๋ธ” ์˜์—ญ ์ €์žฅ ๊ธฐ๋Šฅ + const saveTableAreaData = async (cardId: string, contentRowId: string, contentRow: CardContentRowConfig) => { + const key = `${cardId}-${contentRowId}`; + const rows = externalTableData[key] || []; + + console.log("[RepeatScreenModal] saveTableAreaData ์‹œ์ž‘:", { + key, + rowsCount: rows.length, + contentRowId, + tableDataSource: contentRow?.tableDataSource, + tableCrud: contentRow?.tableCrud, + }); + + if (!contentRow?.tableDataSource?.enabled) { + console.warn("[RepeatScreenModal] ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์†Œ์Šค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์Œ"); + return { success: false, message: "๋ฐ์ดํ„ฐ ์†Œ์Šค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค." }; + } + + const targetTable = contentRow.tableCrud?.targetTable || contentRow.tableDataSource.sourceTable; + const dirtyRows = rows.filter((row) => row._isDirty); + + console.log("[RepeatScreenModal] ์ €์žฅ ๋Œ€์ƒ:", { + targetTable, + dirtyRowsCount: dirtyRows.length, + dirtyRows: dirtyRows.map(r => ({ _isNew: r._isNew, _isDirty: r._isDirty, data: r })), + }); + + if (dirtyRows.length === 0) { + return { success: true, message: "์ €์žฅํ•  ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์—†์Šต๋‹ˆ๋‹ค.", savedCount: 0 }; + } + + const savePromises: Promise[] = []; + const savedIds: number[] = []; + + // ๐Ÿ†• v3.6: editableํ•œ ์ปฌ๋Ÿผ + ์กฐ์ธ ํ‚ค๋งŒ ์ถ”์ถœ (์ฝ๊ธฐ ์ „์šฉ ์ปฌ๋Ÿผ์€ ์ œ์™ธ) + const allowedFields = new Set(); + + // tableColumns์—์„œ editable: true์ธ ํ•„๋“œ๋งŒ ์ถ”๊ฐ€ (์ฝ๊ธฐ ์ „์šฉ ์ปฌ๋Ÿผ ์ œ์™ธ) + if (contentRow.tableColumns) { + contentRow.tableColumns.forEach((col) => { + // editable์ด ๋ช…์‹œ์ ์œผ๋กœ true์ด๊ฑฐ๋‚˜, editable์ด undefined๊ฐ€ ์•„๋‹ˆ๊ณ  false๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ + // ๋˜๋Š” inputType์ด ์žˆ๋Š” ๊ฒฝ์šฐ (์ž…๋ ฅ ๊ฐ€๋Šฅํ•œ ์ปฌ๋Ÿผ) + if (col.field && (col.editable === true || col.inputType)) { + allowedFields.add(col.field); + } + }); + } + + // ์กฐ์ธ ์กฐ๊ฑด์˜ sourceKey ์ถ”๊ฐ€ (์˜ˆ: sales_order_id) - ์ด๊ฑด ํ•ญ์ƒ ํ•„์š” + if (contentRow.tableDataSource?.joinConditions) { + contentRow.tableDataSource.joinConditions.forEach((cond) => { + if (cond.sourceKey) allowedFields.add(cond.sourceKey); + }); + } + + console.log("[RepeatScreenModal] ์ €์žฅ ํ—ˆ์šฉ ํ•„๋“œ (editable + ์กฐ์ธํ‚ค):", Array.from(allowedFields)); + console.log("[RepeatScreenModal] tableColumns ์ •๋ณด:", contentRow.tableColumns?.map(c => ({ + field: c.field, + editable: c.editable, + inputType: c.inputType + }))); + + for (const row of dirtyRows) { + const { _rowId, _originalData, _isDirty, _isNew, ...allData } = row; + + // ํ—ˆ์šฉ๋œ ํ•„๋“œ๋งŒ ํ•„ํ„ฐ๋ง + const dataToSave: Record = {}; + for (const field of allowedFields) { + if (allData[field] !== undefined) { + dataToSave[field] = allData[field]; + } + } + + console.log("[RepeatScreenModal] ์ €์žฅํ•  ๋ฐ์ดํ„ฐ:", { + _isNew, + _originalData, + allData, + dataToSave, + }); + + if (_isNew) { + // INSERT - /add ์—”๋“œํฌ์ธํŠธ ์‚ฌ์šฉ + console.log(`[RepeatScreenModal] INSERT ์š”์ฒญ: /table-management/tables/${targetTable}/add`, dataToSave); + savePromises.push( + apiClient.post(`/table-management/tables/${targetTable}/add`, dataToSave).then((res) => { + console.log("[RepeatScreenModal] INSERT ์‘๋‹ต:", res.data); + if (res.data?.data?.id) { + savedIds.push(res.data.data.id); + } + return res; + }).catch((err) => { + console.error("[RepeatScreenModal] INSERT ์‹คํŒจ:", err.response?.data || err.message); + throw err; + }) + ); + } else if (_originalData?.id) { + // UPDATE - /edit ์—”๋“œํฌ์ธํŠธ ์‚ฌ์šฉ (id๋ฅผ body์— ํฌํ•จ) + const updateData = { ...dataToSave, id: _originalData.id }; + console.log(`[RepeatScreenModal] UPDATE ์š”์ฒญ: /table-management/tables/${targetTable}/edit`, updateData); + savePromises.push( + apiClient.put(`/table-management/tables/${targetTable}/edit`, updateData).then((res) => { + console.log("[RepeatScreenModal] UPDATE ์‘๋‹ต:", res.data); + savedIds.push(_originalData.id); + return res; + }).catch((err) => { + console.error("[RepeatScreenModal] UPDATE ์‹คํŒจ:", err.response?.data || err.message); + throw err; + }) + ); + } + } + + try { + await Promise.all(savePromises); + + // ์ €์žฅ ํ›„ ํ•ด๋‹น ํ‚ค์˜ dirty ํ”Œ๋ž˜๊ทธ๋งŒ ์ดˆ๊ธฐํ™” + setExternalTableData((prev) => { + const updated = { ...prev }; + if (updated[key]) { + updated[key] = updated[key].map((row) => ({ + ...row, + _isDirty: false, + _isNew: false, + _originalData: { ...row, _rowId: undefined, _originalData: undefined, _isDirty: undefined, _isNew: undefined }, + })); + } + return updated; + }); + + return { success: true, message: `${dirtyRows.length}๊ฑด ์ €์žฅ ์™„๋ฃŒ`, savedCount: dirtyRows.length, savedIds }; + } catch (error: any) { + console.error("[RepeatScreenModal] ํ…Œ์ด๋ธ” ์˜์—ญ ์ €์žฅ ์‹คํŒจ:", error); + return { success: false, message: error.message || "์ €์žฅ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค." }; + } + }; + + // ๐Ÿ†• v3.6: ํ…Œ์ด๋ธ” ์˜์—ญ ์ €์žฅ ํ•ธ๋“ค๋Ÿฌ + const handleTableAreaSave = async (cardId: string, contentRowId: string, contentRow: CardContentRowConfig) => { + setIsSaving(true); + try { + const result = await saveTableAreaData(cardId, contentRowId, contentRow); + if (result.success) { + console.log("[RepeatScreenModal] ํ…Œ์ด๋ธ” ์˜์—ญ ์ €์žฅ ์„ฑ๊ณต:", result); + // ์„ฑ๊ณต ์•Œ๋ฆผ (ํ•„์š” ์‹œ toast ์ถ”๊ฐ€) + } else { + console.error("[RepeatScreenModal] ํ…Œ์ด๋ธ” ์˜์—ญ ์ €์žฅ ์‹คํŒจ:", result.message); + // ์‹คํŒจ ์•Œ๋ฆผ (ํ•„์š” ์‹œ toast ์ถ”๊ฐ€) + } + } finally { + setIsSaving(false); + } + }; + // ๐Ÿ†• v3.1: ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ํ–‰ ์‚ญ์ œ ์š”์ฒญ const handleDeleteExternalRowRequest = (cardId: string, rowId: string, contentRowId: string, contentRow: CardContentRowConfig) => { if (contentRow.tableCrud?.deleteConfirm?.enabled !== false) { @@ -1467,33 +1630,41 @@ export function RepeatScreenModalComponent({ {contentRow.type === "table" && contentRow.tableDataSource?.enabled ? ( // ๐Ÿ†• v3.1: ์™ธ๋ถ€ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์†Œ์Šค ์‚ฌ์šฉ
- {contentRow.tableTitle && ( + {/* ํ…Œ์ด๋ธ” ํ—ค๋” ์˜์—ญ: ์ œ๋ชฉ + ๋ฒ„ํŠผ๋“ค */} + {(contentRow.tableTitle || contentRow.tableCrud?.allowSave || contentRow.tableCrud?.allowCreate) && (
- {contentRow.tableTitle} - {contentRow.tableCrud?.allowCreate && ( - - )} -
- )} - {!contentRow.tableTitle && contentRow.tableCrud?.allowCreate && ( -
- + {contentRow.tableTitle || ""} +
+ {/* ์ €์žฅ ๋ฒ„ํŠผ - allowSave๊ฐ€ true์ผ ๋•Œ๋งŒ ํ‘œ์‹œ */} + {contentRow.tableCrud?.allowSave && ( + + )} + {/* ์ถ”๊ฐ€ ๋ฒ„ํŠผ */} + {contentRow.tableCrud?.allowCreate && ( + + )} +
)} @@ -2243,10 +2414,12 @@ function renderTableCell(col: TableColumnConfig, row: CardRowData, onChange: (va /> ); case "date": + // ISO 8601 ํ˜•์‹('2025-12-02T00:00:00.000Z')์„ 'YYYY-MM-DD' ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ + const dateValue = value ? (typeof value === 'string' && value.includes('T') ? value.split('T')[0] : value) : ""; return ( onChange(e.target.value)} className="h-8 text-sm" /> @@ -2298,7 +2471,7 @@ function renderColumn(col: CardColumnConfig, card: CardData, onChange: (value: a {col.type === "date" && ( onChange(e.target.value)} className="h-10 text-sm" /> diff --git a/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalConfigPanel.tsx b/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalConfigPanel.tsx index da7088a9..54949627 100644 --- a/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalConfigPanel.tsx +++ b/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalConfigPanel.tsx @@ -2568,13 +2568,13 @@ function ContentRowConfigSection({ {/* CRUD ์„ค์ • */}
-
+
onUpdateRow({ - tableCrud: { ...row.tableCrud, allowCreate: checked, allowUpdate: row.tableCrud?.allowUpdate || false, allowDelete: row.tableCrud?.allowDelete || false }, + tableCrud: { ...row.tableCrud, allowCreate: checked, allowUpdate: row.tableCrud?.allowUpdate || false, allowDelete: row.tableCrud?.allowDelete || false, allowSave: row.tableCrud?.allowSave || false }, }) } className="scale-[0.5]" @@ -2586,7 +2586,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 }, + tableCrud: { ...row.tableCrud, allowCreate: row.tableCrud?.allowCreate || false, allowUpdate: checked, allowDelete: row.tableCrud?.allowDelete || false, allowSave: row.tableCrud?.allowSave || false }, }) } className="scale-[0.5]" @@ -2598,13 +2598,25 @@ 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 }, + tableCrud: { ...row.tableCrud, allowCreate: row.tableCrud?.allowCreate || false, allowUpdate: row.tableCrud?.allowUpdate || false, allowDelete: checked, allowSave: row.tableCrud?.allowSave || false }, }) } 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 && (
diff --git a/frontend/lib/registry/components/repeat-screen-modal/types.ts b/frontend/lib/registry/components/repeat-screen-modal/types.ts index 81a36366..7226503e 100644 --- a/frontend/lib/registry/components/repeat-screen-modal/types.ts +++ b/frontend/lib/registry/components/repeat-screen-modal/types.ts @@ -188,6 +188,10 @@ export interface TableCrudConfig { allowUpdate: boolean; // ํ–‰ ์ˆ˜์ • ํ—ˆ์šฉ allowDelete: boolean; // ํ–‰ ์‚ญ์ œ ํ—ˆ์šฉ + // ๐Ÿ†• v3.5: ํ…Œ์ด๋ธ” ์˜์—ญ ์ €์žฅ ๋ฒ„ํŠผ + allowSave?: boolean; // ํ…Œ์ด๋ธ” ์˜์—ญ์— ์ €์žฅ ๋ฒ„ํŠผ ํ‘œ์‹œ + saveButtonLabel?: string; // ์ €์žฅ ๋ฒ„ํŠผ ๋ผ๋ฒจ (๊ธฐ๋ณธ: "์ €์žฅ") + // ์‹ ๊ทœ ํ–‰ ๊ธฐ๋ณธ๊ฐ’ newRowDefaults?: Record; // ๊ธฐ๋ณธ๊ฐ’ (์˜ˆ: { status: "READY", sales_order_id: "{id}" })