삭제버튼 제어 동작하지 않던 오류 수정
This commit is contained in:
parent
80cf20e142
commit
ee3a648917
|
|
@ -231,7 +231,7 @@ export const deleteFormData = async (
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { companyCode, userId } = req.user as any;
|
const { companyCode, userId } = req.user as any;
|
||||||
const { tableName } = req.body;
|
const { tableName, screenId } = req.body;
|
||||||
|
|
||||||
if (!tableName) {
|
if (!tableName) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
|
|
@ -240,7 +240,16 @@ export const deleteFormData = async (
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await dynamicFormService.deleteFormData(id, tableName, companyCode, userId); // userId 추가
|
// screenId를 숫자로 변환 (문자열로 전달될 수 있음)
|
||||||
|
const parsedScreenId = screenId ? parseInt(screenId, 10) : undefined;
|
||||||
|
|
||||||
|
await dynamicFormService.deleteFormData(
|
||||||
|
id,
|
||||||
|
tableName,
|
||||||
|
companyCode,
|
||||||
|
userId,
|
||||||
|
parsedScreenId // screenId 추가 (제어관리 실행용)
|
||||||
|
);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|
|
||||||
|
|
@ -1192,12 +1192,18 @@ export class DynamicFormService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 폼 데이터 삭제 (실제 테이블에서 직접 삭제)
|
* 폼 데이터 삭제 (실제 테이블에서 직접 삭제)
|
||||||
|
* @param id 삭제할 레코드 ID
|
||||||
|
* @param tableName 테이블명
|
||||||
|
* @param companyCode 회사 코드
|
||||||
|
* @param userId 사용자 ID
|
||||||
|
* @param screenId 화면 ID (제어관리 실행용, 선택사항)
|
||||||
*/
|
*/
|
||||||
async deleteFormData(
|
async deleteFormData(
|
||||||
id: string | number,
|
id: string | number,
|
||||||
tableName: string,
|
tableName: string,
|
||||||
companyCode?: string,
|
companyCode?: string,
|
||||||
userId?: string
|
userId?: string,
|
||||||
|
screenId?: number
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
console.log("🗑️ 서비스: 실제 테이블에서 폼 데이터 삭제 시작:", {
|
console.log("🗑️ 서비스: 실제 테이블에서 폼 데이터 삭제 시작:", {
|
||||||
|
|
@ -1310,14 +1316,19 @@ export class DynamicFormService {
|
||||||
const recordCompanyCode =
|
const recordCompanyCode =
|
||||||
deletedRecord?.company_code || companyCode || "*";
|
deletedRecord?.company_code || companyCode || "*";
|
||||||
|
|
||||||
await this.executeDataflowControlIfConfigured(
|
// screenId가 전달되지 않으면 제어관리를 실행하지 않음
|
||||||
0, // DELETE는 screenId를 알 수 없으므로 0으로 설정 (추후 개선 필요)
|
if (screenId && screenId > 0) {
|
||||||
tableName,
|
await this.executeDataflowControlIfConfigured(
|
||||||
deletedRecord,
|
screenId,
|
||||||
"delete",
|
tableName,
|
||||||
userId || "system",
|
deletedRecord,
|
||||||
recordCompanyCode
|
"delete",
|
||||||
);
|
userId || "system",
|
||||||
|
recordCompanyCode
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log("ℹ️ screenId가 전달되지 않아 제어관리를 건너뜁니다. (screenId:", screenId, ")");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (controlError) {
|
} catch (controlError) {
|
||||||
console.error("⚠️ 제어관리 실행 오류:", controlError);
|
console.error("⚠️ 제어관리 실행 오류:", controlError);
|
||||||
|
|
@ -1662,10 +1673,16 @@ export class DynamicFormService {
|
||||||
!!properties?.webTypeConfig?.dataflowConfig?.flowControls,
|
!!properties?.webTypeConfig?.dataflowConfig?.flowControls,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 버튼 컴포넌트이고 저장 액션이며 제어관리가 활성화된 경우
|
// 버튼 컴포넌트이고 제어관리가 활성화된 경우
|
||||||
|
// triggerType에 맞는 액션 타입 매칭: insert/update -> save, delete -> delete
|
||||||
|
const buttonActionType = properties?.componentConfig?.action?.type;
|
||||||
|
const isMatchingAction =
|
||||||
|
(triggerType === "delete" && buttonActionType === "delete") ||
|
||||||
|
((triggerType === "insert" || triggerType === "update") && buttonActionType === "save");
|
||||||
|
|
||||||
if (
|
if (
|
||||||
properties?.componentType === "button-primary" &&
|
properties?.componentType === "button-primary" &&
|
||||||
properties?.componentConfig?.action?.type === "save" &&
|
isMatchingAction &&
|
||||||
properties?.webTypeConfig?.enableDataflowControl === true
|
properties?.webTypeConfig?.enableDataflowControl === true
|
||||||
) {
|
) {
|
||||||
const dataflowConfig = properties?.webTypeConfig?.dataflowConfig;
|
const dataflowConfig = properties?.webTypeConfig?.dataflowConfig;
|
||||||
|
|
|
||||||
|
|
@ -969,21 +969,56 @@ export class NodeFlowExecutionService {
|
||||||
const insertedData = { ...data };
|
const insertedData = { ...data };
|
||||||
|
|
||||||
console.log("🗺️ 필드 매핑 처리 중...");
|
console.log("🗺️ 필드 매핑 처리 중...");
|
||||||
fieldMappings.forEach((mapping: any) => {
|
|
||||||
|
// 🔥 채번 규칙 서비스 동적 import
|
||||||
|
const { numberingRuleService } = await import("./numberingRuleService");
|
||||||
|
|
||||||
|
for (const mapping of fieldMappings) {
|
||||||
fields.push(mapping.targetField);
|
fields.push(mapping.targetField);
|
||||||
const value =
|
let value: any;
|
||||||
mapping.staticValue !== undefined
|
|
||||||
? mapping.staticValue
|
// 🔥 값 생성 유형에 따른 처리
|
||||||
: data[mapping.sourceField];
|
const valueType = mapping.valueType || (mapping.staticValue !== undefined ? "static" : "source");
|
||||||
|
|
||||||
console.log(
|
if (valueType === "autoGenerate" && mapping.numberingRuleId) {
|
||||||
` ${mapping.sourceField} → ${mapping.targetField}: ${value === undefined ? "❌ undefined" : "✅ " + value}`
|
// 자동 생성 (채번 규칙)
|
||||||
);
|
const companyCode = context.buttonContext?.companyCode || "*";
|
||||||
|
try {
|
||||||
|
value = await numberingRuleService.allocateCode(
|
||||||
|
mapping.numberingRuleId,
|
||||||
|
companyCode
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
` 🔢 자동 생성(채번): ${mapping.targetField} = ${value} (규칙: ${mapping.numberingRuleId})`
|
||||||
|
);
|
||||||
|
} catch (error: any) {
|
||||||
|
logger.error(`채번 규칙 적용 실패: ${error.message}`);
|
||||||
|
console.error(
|
||||||
|
` ❌ 채번 실패 → ${mapping.targetField}: ${error.message}`
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
`채번 규칙 '${mapping.numberingRuleName || mapping.numberingRuleId}' 적용 실패: ${error.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (valueType === "static" || mapping.staticValue !== undefined) {
|
||||||
|
// 고정값
|
||||||
|
value = mapping.staticValue;
|
||||||
|
console.log(
|
||||||
|
` 📌 고정값: ${mapping.targetField} = ${value}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 소스 필드
|
||||||
|
value = data[mapping.sourceField];
|
||||||
|
console.log(
|
||||||
|
` ${mapping.sourceField} → ${mapping.targetField}: ${value === undefined ? "❌ undefined" : "✅ " + value}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
values.push(value);
|
values.push(value);
|
||||||
|
|
||||||
// 🔥 삽입된 값을 데이터에 반영
|
// 🔥 삽입된 값을 데이터에 반영
|
||||||
insertedData[mapping.targetField] = value;
|
insertedData[mapping.targetField] = value;
|
||||||
});
|
}
|
||||||
|
|
||||||
// 🆕 writer와 company_code 자동 추가 (필드 매핑에 없는 경우)
|
// 🆕 writer와 company_code 자동 추가 (필드 매핑에 없는 경우)
|
||||||
const hasWriterMapping = fieldMappings.some(
|
const hasWriterMapping = fieldMappings.some(
|
||||||
|
|
@ -1528,16 +1563,24 @@ export class NodeFlowExecutionService {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 🔑 Primary Key 자동 추가 (context-data 모드)
|
// 🔑 Primary Key 자동 추가 여부 결정:
|
||||||
console.log("🔑 context-data 모드: Primary Key 자동 추가");
|
// whereConditions가 명시적으로 설정되어 있으면 PK 자동 추가를 하지 않음
|
||||||
const enhancedWhereConditions = await this.enhanceWhereConditionsWithPK(
|
// (사용자가 직접 조건을 설정한 경우 의도를 존중)
|
||||||
whereConditions,
|
let finalWhereConditions: any[];
|
||||||
data,
|
if (whereConditions && whereConditions.length > 0) {
|
||||||
targetTable
|
console.log("📋 사용자 정의 WHERE 조건 사용 (PK 자동 추가 안 함)");
|
||||||
);
|
finalWhereConditions = whereConditions;
|
||||||
|
} else {
|
||||||
|
console.log("🔑 context-data 모드: Primary Key 자동 추가");
|
||||||
|
finalWhereConditions = await this.enhanceWhereConditionsWithPK(
|
||||||
|
whereConditions,
|
||||||
|
data,
|
||||||
|
targetTable
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const whereResult = this.buildWhereClause(
|
const whereResult = this.buildWhereClause(
|
||||||
enhancedWhereConditions,
|
finalWhereConditions,
|
||||||
data,
|
data,
|
||||||
paramIndex
|
paramIndex
|
||||||
);
|
);
|
||||||
|
|
@ -1907,22 +1950,30 @@ export class NodeFlowExecutionService {
|
||||||
return deletedDataArray;
|
return deletedDataArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🆕 context-data 모드: 개별 삭제 (PK 자동 추가)
|
// 🆕 context-data 모드: 개별 삭제
|
||||||
console.log("🎯 context-data 모드: 개별 삭제 시작");
|
console.log("🎯 context-data 모드: 개별 삭제 시작");
|
||||||
|
|
||||||
for (const data of dataArray) {
|
for (const data of dataArray) {
|
||||||
console.log("🔍 WHERE 조건 처리 중...");
|
console.log("🔍 WHERE 조건 처리 중...");
|
||||||
|
|
||||||
// 🔑 Primary Key 자동 추가 (context-data 모드)
|
// 🔑 Primary Key 자동 추가 여부 결정:
|
||||||
console.log("🔑 context-data 모드: Primary Key 자동 추가");
|
// whereConditions가 명시적으로 설정되어 있으면 PK 자동 추가를 하지 않음
|
||||||
const enhancedWhereConditions = await this.enhanceWhereConditionsWithPK(
|
// (사용자가 직접 조건을 설정한 경우 의도를 존중)
|
||||||
whereConditions,
|
let finalWhereConditions: any[];
|
||||||
data,
|
if (whereConditions && whereConditions.length > 0) {
|
||||||
targetTable
|
console.log("📋 사용자 정의 WHERE 조건 사용 (PK 자동 추가 안 함)");
|
||||||
);
|
finalWhereConditions = whereConditions;
|
||||||
|
} else {
|
||||||
|
console.log("🔑 context-data 모드: Primary Key 자동 추가");
|
||||||
|
finalWhereConditions = await this.enhanceWhereConditionsWithPK(
|
||||||
|
whereConditions,
|
||||||
|
data,
|
||||||
|
targetTable
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const whereResult = this.buildWhereClause(
|
const whereResult = this.buildWhereClause(
|
||||||
enhancedWhereConditions,
|
finalWhereConditions,
|
||||||
data,
|
data,
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
|
|
@ -2865,10 +2916,11 @@ export class NodeFlowExecutionService {
|
||||||
|
|
||||||
if (fieldValue === null || fieldValue === undefined || fieldValue === "") {
|
if (fieldValue === null || fieldValue === undefined || fieldValue === "") {
|
||||||
logger.info(
|
logger.info(
|
||||||
`⚠️ EXISTS 조건: 필드값이 비어있어 ${operator === "NOT_EXISTS_IN" ? "TRUE" : "FALSE"} 반환`
|
`⚠️ EXISTS 조건: 필드값이 비어있어 FALSE 반환 (빈 값은 조건 검사하지 않음)`
|
||||||
);
|
);
|
||||||
// 값이 비어있으면: EXISTS_IN은 false, NOT_EXISTS_IN은 true
|
// 값이 비어있으면 조건 검사 자체가 무의미하므로 항상 false 반환
|
||||||
return operator === "NOT_EXISTS_IN";
|
// 이렇게 하면 빈 값으로 인한 의도치 않은 INSERT/UPDATE/DELETE가 방지됨
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Plus, Trash2, Check, ChevronsUpDown, ArrowRight, Database, Globe, Link2 } from "lucide-react";
|
import { Plus, Trash2, Check, ChevronsUpDown, ArrowRight, Database, Globe, Link2, Sparkles } from "lucide-react";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
@ -18,6 +18,8 @@ import { cn } from "@/lib/utils";
|
||||||
import { useFlowEditorStore } from "@/lib/stores/flowEditorStore";
|
import { useFlowEditorStore } from "@/lib/stores/flowEditorStore";
|
||||||
import { tableTypeApi } from "@/lib/api/screen";
|
import { tableTypeApi } from "@/lib/api/screen";
|
||||||
import { getTestedExternalConnections, getExternalTables, getExternalColumns } from "@/lib/api/nodeExternalConnections";
|
import { getTestedExternalConnections, getExternalTables, getExternalColumns } from "@/lib/api/nodeExternalConnections";
|
||||||
|
import { getNumberingRules } from "@/lib/api/numberingRule";
|
||||||
|
import type { NumberingRuleConfig } from "@/types/numbering-rule";
|
||||||
import type { InsertActionNodeData } from "@/types/node-editor";
|
import type { InsertActionNodeData } from "@/types/node-editor";
|
||||||
import type { ExternalConnection, ExternalTable, ExternalColumn } from "@/lib/api/nodeExternalConnections";
|
import type { ExternalConnection, ExternalTable, ExternalColumn } from "@/lib/api/nodeExternalConnections";
|
||||||
|
|
||||||
|
|
@ -89,6 +91,11 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP
|
||||||
const [apiHeaders, setApiHeaders] = useState<Record<string, string>>(data.apiHeaders || {});
|
const [apiHeaders, setApiHeaders] = useState<Record<string, string>>(data.apiHeaders || {});
|
||||||
const [apiBodyTemplate, setApiBodyTemplate] = useState(data.apiBodyTemplate || "");
|
const [apiBodyTemplate, setApiBodyTemplate] = useState(data.apiBodyTemplate || "");
|
||||||
|
|
||||||
|
// 🔥 채번 규칙 관련 상태
|
||||||
|
const [numberingRules, setNumberingRules] = useState<NumberingRuleConfig[]>([]);
|
||||||
|
const [numberingRulesLoading, setNumberingRulesLoading] = useState(false);
|
||||||
|
const [mappingNumberingRulesOpenState, setMappingNumberingRulesOpenState] = useState<boolean[]>([]);
|
||||||
|
|
||||||
// 데이터 변경 시 로컬 상태 업데이트
|
// 데이터 변경 시 로컬 상태 업데이트
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDisplayName(data.displayName || data.targetTable);
|
setDisplayName(data.displayName || data.targetTable);
|
||||||
|
|
@ -128,8 +135,33 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMappingSourceFieldsOpenState(new Array(fieldMappings.length).fill(false));
|
setMappingSourceFieldsOpenState(new Array(fieldMappings.length).fill(false));
|
||||||
setMappingTargetFieldsOpenState(new Array(fieldMappings.length).fill(false));
|
setMappingTargetFieldsOpenState(new Array(fieldMappings.length).fill(false));
|
||||||
|
setMappingNumberingRulesOpenState(new Array(fieldMappings.length).fill(false));
|
||||||
}, [fieldMappings.length]);
|
}, [fieldMappings.length]);
|
||||||
|
|
||||||
|
// 🔥 채번 규칙 로딩 (자동 생성 사용 시)
|
||||||
|
useEffect(() => {
|
||||||
|
const loadNumberingRules = async () => {
|
||||||
|
setNumberingRulesLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await getNumberingRules();
|
||||||
|
if (response.success && response.data) {
|
||||||
|
setNumberingRules(response.data);
|
||||||
|
console.log(`✅ 채번 규칙 ${response.data.length}개 로딩 완료`);
|
||||||
|
} else {
|
||||||
|
console.error("❌ 채번 규칙 로딩 실패:", response.error);
|
||||||
|
setNumberingRules([]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ 채번 규칙 로딩 오류:", error);
|
||||||
|
setNumberingRules([]);
|
||||||
|
} finally {
|
||||||
|
setNumberingRulesLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadNumberingRules();
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 🔥 외부 테이블 변경 시 컬럼 로드
|
// 🔥 외부 테이블 변경 시 컬럼 로드
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (targetType === "external" && selectedExternalConnectionId && externalTargetTable) {
|
if (targetType === "external" && selectedExternalConnectionId && externalTargetTable) {
|
||||||
|
|
@ -540,6 +572,7 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP
|
||||||
sourceField: null,
|
sourceField: null,
|
||||||
targetField: "",
|
targetField: "",
|
||||||
staticValue: undefined,
|
staticValue: undefined,
|
||||||
|
valueType: "source" as const, // 🔥 기본값: 소스 필드
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
setFieldMappings(newMappings);
|
setFieldMappings(newMappings);
|
||||||
|
|
@ -548,6 +581,7 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP
|
||||||
// Combobox 열림 상태 배열 초기화
|
// Combobox 열림 상태 배열 초기화
|
||||||
setMappingSourceFieldsOpenState(new Array(newMappings.length).fill(false));
|
setMappingSourceFieldsOpenState(new Array(newMappings.length).fill(false));
|
||||||
setMappingTargetFieldsOpenState(new Array(newMappings.length).fill(false));
|
setMappingTargetFieldsOpenState(new Array(newMappings.length).fill(false));
|
||||||
|
setMappingNumberingRulesOpenState(new Array(newMappings.length).fill(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveMapping = (index: number) => {
|
const handleRemoveMapping = (index: number) => {
|
||||||
|
|
@ -558,6 +592,7 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP
|
||||||
// Combobox 열림 상태 배열도 업데이트
|
// Combobox 열림 상태 배열도 업데이트
|
||||||
setMappingSourceFieldsOpenState(new Array(newMappings.length).fill(false));
|
setMappingSourceFieldsOpenState(new Array(newMappings.length).fill(false));
|
||||||
setMappingTargetFieldsOpenState(new Array(newMappings.length).fill(false));
|
setMappingTargetFieldsOpenState(new Array(newMappings.length).fill(false));
|
||||||
|
setMappingNumberingRulesOpenState(new Array(newMappings.length).fill(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMappingChange = (index: number, field: string, value: any) => {
|
const handleMappingChange = (index: number, field: string, value: any) => {
|
||||||
|
|
@ -586,6 +621,24 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP
|
||||||
targetField: value,
|
targetField: value,
|
||||||
targetFieldLabel: targetColumn?.label_ko || targetColumn?.column_label || targetColumn?.displayName || value,
|
targetFieldLabel: targetColumn?.label_ko || targetColumn?.column_label || targetColumn?.displayName || value,
|
||||||
};
|
};
|
||||||
|
} else if (field === "valueType") {
|
||||||
|
// 🔥 값 생성 유형 변경 시 관련 필드 초기화
|
||||||
|
newMappings[index] = {
|
||||||
|
...newMappings[index],
|
||||||
|
valueType: value,
|
||||||
|
// 유형 변경 시 다른 유형의 값 초기화
|
||||||
|
...(value !== "source" && { sourceField: null, sourceFieldLabel: undefined }),
|
||||||
|
...(value !== "static" && { staticValue: undefined }),
|
||||||
|
...(value !== "autoGenerate" && { numberingRuleId: undefined, numberingRuleName: undefined }),
|
||||||
|
};
|
||||||
|
} else if (field === "numberingRuleId") {
|
||||||
|
// 🔥 채번 규칙 선택 시 이름도 함께 저장
|
||||||
|
const selectedRule = numberingRules.find((r) => r.ruleId === value);
|
||||||
|
newMappings[index] = {
|
||||||
|
...newMappings[index],
|
||||||
|
numberingRuleId: value,
|
||||||
|
numberingRuleName: selectedRule?.ruleName,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
newMappings[index] = {
|
newMappings[index] = {
|
||||||
...newMappings[index],
|
...newMappings[index],
|
||||||
|
|
@ -1165,54 +1218,203 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{/* 소스 필드 입력/선택 */}
|
{/* 🔥 값 생성 유형 선택 */}
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-xs text-gray-600">
|
<Label className="text-xs text-gray-600">값 생성 방식</Label>
|
||||||
소스 필드
|
<div className="mt-1 grid grid-cols-3 gap-1">
|
||||||
{hasRestAPISource && <span className="ml-1 text-teal-600">(REST API - 직접 입력)</span>}
|
<button
|
||||||
</Label>
|
type="button"
|
||||||
{hasRestAPISource ? (
|
onClick={() => handleMappingChange(index, "valueType", "source")}
|
||||||
// REST API 소스인 경우: 직접 입력
|
className={cn(
|
||||||
|
"rounded border px-2 py-1 text-xs transition-all",
|
||||||
|
(mapping.valueType === "source" || !mapping.valueType)
|
||||||
|
? "border-blue-500 bg-blue-50 text-blue-700"
|
||||||
|
: "border-gray-200 hover:border-gray-300",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
소스 필드
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleMappingChange(index, "valueType", "static")}
|
||||||
|
className={cn(
|
||||||
|
"rounded border px-2 py-1 text-xs transition-all",
|
||||||
|
mapping.valueType === "static"
|
||||||
|
? "border-orange-500 bg-orange-50 text-orange-700"
|
||||||
|
: "border-gray-200 hover:border-gray-300",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
고정값
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleMappingChange(index, "valueType", "autoGenerate")}
|
||||||
|
className={cn(
|
||||||
|
"rounded border px-2 py-1 text-xs transition-all flex items-center justify-center gap-1",
|
||||||
|
mapping.valueType === "autoGenerate"
|
||||||
|
? "border-purple-500 bg-purple-50 text-purple-700"
|
||||||
|
: "border-gray-200 hover:border-gray-300",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Sparkles className="h-3 w-3" />
|
||||||
|
자동생성
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 🔥 소스 필드 입력/선택 (valueType === "source" 일 때만) */}
|
||||||
|
{(mapping.valueType === "source" || !mapping.valueType) && (
|
||||||
|
<div>
|
||||||
|
<Label className="text-xs text-gray-600">
|
||||||
|
소스 필드
|
||||||
|
{hasRestAPISource && <span className="ml-1 text-teal-600">(REST API - 직접 입력)</span>}
|
||||||
|
</Label>
|
||||||
|
{hasRestAPISource ? (
|
||||||
|
// REST API 소스인 경우: 직접 입력
|
||||||
|
<Input
|
||||||
|
value={mapping.sourceField || ""}
|
||||||
|
onChange={(e) => handleMappingChange(index, "sourceField", e.target.value || null)}
|
||||||
|
placeholder="필드명 입력 (예: userId, userName)"
|
||||||
|
className="mt-1 h-8 text-xs"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
// 일반 소스인 경우: Combobox 선택
|
||||||
|
<Popover
|
||||||
|
open={mappingSourceFieldsOpenState[index]}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
const newState = [...mappingSourceFieldsOpenState];
|
||||||
|
newState[index] = open;
|
||||||
|
setMappingSourceFieldsOpenState(newState);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
aria-expanded={mappingSourceFieldsOpenState[index]}
|
||||||
|
className="mt-1 h-8 w-full justify-between text-xs sm:h-10 sm:text-sm"
|
||||||
|
>
|
||||||
|
{mapping.sourceField
|
||||||
|
? (() => {
|
||||||
|
const field = sourceFields.find((f) => f.name === mapping.sourceField);
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between gap-2 overflow-hidden">
|
||||||
|
<span className="truncate font-medium">
|
||||||
|
{field?.label || mapping.sourceField}
|
||||||
|
</span>
|
||||||
|
{field?.label && field.label !== field.name && (
|
||||||
|
<span className="text-muted-foreground font-mono text-xs">
|
||||||
|
{field.name}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()
|
||||||
|
: "소스 필드 선택"}
|
||||||
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
className="p-0"
|
||||||
|
style={{ width: "var(--radix-popover-trigger-width)" }}
|
||||||
|
align="start"
|
||||||
|
>
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="소스 필드 검색..." className="text-xs sm:text-sm" />
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty className="text-xs sm:text-sm">
|
||||||
|
필드를 찾을 수 없습니다.
|
||||||
|
</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{sourceFields.map((field) => (
|
||||||
|
<CommandItem
|
||||||
|
key={field.name}
|
||||||
|
value={field.name}
|
||||||
|
onSelect={(currentValue) => {
|
||||||
|
handleMappingChange(index, "sourceField", currentValue || null);
|
||||||
|
const newState = [...mappingSourceFieldsOpenState];
|
||||||
|
newState[index] = false;
|
||||||
|
setMappingSourceFieldsOpenState(newState);
|
||||||
|
}}
|
||||||
|
className="text-xs sm:text-sm"
|
||||||
|
>
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
"mr-2 h-4 w-4",
|
||||||
|
mapping.sourceField === field.name ? "opacity-100" : "opacity-0",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="font-medium">{field.label || field.name}</span>
|
||||||
|
{field.label && field.label !== field.name && (
|
||||||
|
<span className="text-muted-foreground font-mono text-[10px]">
|
||||||
|
{field.name}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
{hasRestAPISource && (
|
||||||
|
<p className="mt-1 text-xs text-gray-500">API 응답 JSON의 필드명을 입력하세요</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 🔥 고정값 입력 (valueType === "static" 일 때) */}
|
||||||
|
{mapping.valueType === "static" && (
|
||||||
|
<div>
|
||||||
|
<Label className="text-xs text-gray-600">고정값</Label>
|
||||||
<Input
|
<Input
|
||||||
value={mapping.sourceField || ""}
|
value={mapping.staticValue || ""}
|
||||||
onChange={(e) => handleMappingChange(index, "sourceField", e.target.value || null)}
|
onChange={(e) => handleMappingChange(index, "staticValue", e.target.value || undefined)}
|
||||||
placeholder="필드명 입력 (예: userId, userName)"
|
placeholder="고정값 입력"
|
||||||
className="mt-1 h-8 text-xs"
|
className="mt-1 h-8 text-xs"
|
||||||
/>
|
/>
|
||||||
) : (
|
</div>
|
||||||
// 일반 소스인 경우: Combobox 선택
|
)}
|
||||||
|
|
||||||
|
{/* 🔥 채번 규칙 선택 (valueType === "autoGenerate" 일 때) */}
|
||||||
|
{mapping.valueType === "autoGenerate" && (
|
||||||
|
<div>
|
||||||
|
<Label className="text-xs text-gray-600">
|
||||||
|
채번 규칙
|
||||||
|
{numberingRulesLoading && <span className="ml-1 text-gray-400">(로딩 중...)</span>}
|
||||||
|
</Label>
|
||||||
<Popover
|
<Popover
|
||||||
open={mappingSourceFieldsOpenState[index]}
|
open={mappingNumberingRulesOpenState[index]}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
const newState = [...mappingSourceFieldsOpenState];
|
const newState = [...mappingNumberingRulesOpenState];
|
||||||
newState[index] = open;
|
newState[index] = open;
|
||||||
setMappingSourceFieldsOpenState(newState);
|
setMappingNumberingRulesOpenState(newState);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
aria-expanded={mappingSourceFieldsOpenState[index]}
|
aria-expanded={mappingNumberingRulesOpenState[index]}
|
||||||
className="mt-1 h-8 w-full justify-between text-xs sm:h-10 sm:text-sm"
|
className="mt-1 h-8 w-full justify-between text-xs sm:h-10 sm:text-sm"
|
||||||
|
disabled={numberingRulesLoading || numberingRules.length === 0}
|
||||||
>
|
>
|
||||||
{mapping.sourceField
|
{mapping.numberingRuleId
|
||||||
? (() => {
|
? (() => {
|
||||||
const field = sourceFields.find((f) => f.name === mapping.sourceField);
|
const rule = numberingRules.find((r) => r.ruleId === mapping.numberingRuleId);
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between gap-2 overflow-hidden">
|
<div className="flex items-center gap-2 overflow-hidden">
|
||||||
|
<Sparkles className="h-3 w-3 text-purple-500" />
|
||||||
<span className="truncate font-medium">
|
<span className="truncate font-medium">
|
||||||
{field?.label || mapping.sourceField}
|
{rule?.ruleName || mapping.numberingRuleName || mapping.numberingRuleId}
|
||||||
</span>
|
</span>
|
||||||
{field?.label && field.label !== field.name && (
|
|
||||||
<span className="text-muted-foreground font-mono text-xs">
|
|
||||||
{field.name}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})()
|
})()
|
||||||
: "소스 필드 선택"}
|
: "채번 규칙 선택"}
|
||||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
|
|
@ -1222,37 +1424,36 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP
|
||||||
align="start"
|
align="start"
|
||||||
>
|
>
|
||||||
<Command>
|
<Command>
|
||||||
<CommandInput placeholder="소스 필드 검색..." className="text-xs sm:text-sm" />
|
<CommandInput placeholder="채번 규칙 검색..." className="text-xs sm:text-sm" />
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty className="text-xs sm:text-sm">
|
<CommandEmpty className="text-xs sm:text-sm">
|
||||||
필드를 찾을 수 없습니다.
|
채번 규칙을 찾을 수 없습니다.
|
||||||
</CommandEmpty>
|
</CommandEmpty>
|
||||||
<CommandGroup>
|
<CommandGroup>
|
||||||
{sourceFields.map((field) => (
|
{numberingRules.map((rule) => (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
key={field.name}
|
key={rule.ruleId}
|
||||||
value={field.name}
|
value={rule.ruleId}
|
||||||
onSelect={(currentValue) => {
|
onSelect={(currentValue) => {
|
||||||
handleMappingChange(index, "sourceField", currentValue || null);
|
handleMappingChange(index, "numberingRuleId", currentValue);
|
||||||
const newState = [...mappingSourceFieldsOpenState];
|
const newState = [...mappingNumberingRulesOpenState];
|
||||||
newState[index] = false;
|
newState[index] = false;
|
||||||
setMappingSourceFieldsOpenState(newState);
|
setMappingNumberingRulesOpenState(newState);
|
||||||
}}
|
}}
|
||||||
className="text-xs sm:text-sm"
|
className="text-xs sm:text-sm"
|
||||||
>
|
>
|
||||||
<Check
|
<Check
|
||||||
className={cn(
|
className={cn(
|
||||||
"mr-2 h-4 w-4",
|
"mr-2 h-4 w-4",
|
||||||
mapping.sourceField === field.name ? "opacity-100" : "opacity-0",
|
mapping.numberingRuleId === rule.ruleId ? "opacity-100" : "opacity-0",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="font-medium">{field.label || field.name}</span>
|
<span className="font-medium">{rule.ruleName}</span>
|
||||||
{field.label && field.label !== field.name && (
|
<span className="text-muted-foreground font-mono text-[10px]">
|
||||||
<span className="text-muted-foreground font-mono text-[10px]">
|
{rule.ruleId}
|
||||||
{field.name}
|
{rule.tableName && ` - ${rule.tableName}`}
|
||||||
</span>
|
</span>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
))}
|
))}
|
||||||
|
|
@ -1261,11 +1462,13 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP
|
||||||
</Command>
|
</Command>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
)}
|
{numberingRules.length === 0 && !numberingRulesLoading && (
|
||||||
{hasRestAPISource && (
|
<p className="mt-1 text-xs text-orange-600">
|
||||||
<p className="mt-1 text-xs text-gray-500">API 응답 JSON의 필드명을 입력하세요</p>
|
등록된 채번 규칙이 없습니다. 시스템 관리에서 먼저 채번 규칙을 생성하세요.
|
||||||
)}
|
</p>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex items-center justify-center py-1">
|
<div className="flex items-center justify-center py-1">
|
||||||
<ArrowRight className="h-4 w-4 text-green-600" />
|
<ArrowRight className="h-4 w-4 text-green-600" />
|
||||||
|
|
@ -1400,18 +1603,6 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 정적 값 */}
|
|
||||||
<div>
|
|
||||||
<Label className="text-xs text-gray-600">정적 값 (선택)</Label>
|
|
||||||
<Input
|
|
||||||
value={mapping.staticValue || ""}
|
|
||||||
onChange={(e) => handleMappingChange(index, "staticValue", e.target.value || undefined)}
|
|
||||||
placeholder="소스 필드 대신 고정 값 사용"
|
|
||||||
className="mt-1 h-8 text-xs"
|
|
||||||
/>
|
|
||||||
<p className="mt-1 text-xs text-gray-400">소스 필드가 비어있을 때만 사용됩니다</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -1428,9 +1619,8 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP
|
||||||
|
|
||||||
{/* 안내 */}
|
{/* 안내 */}
|
||||||
<div className="rounded bg-green-50 p-3 text-xs text-green-700">
|
<div className="rounded bg-green-50 p-3 text-xs text-green-700">
|
||||||
✅ 테이블과 필드는 실제 데이터베이스에서 조회됩니다.
|
<p>테이블과 필드는 실제 데이터베이스에서 조회됩니다.</p>
|
||||||
<br />
|
<p className="mt-1">값 생성 방식: 소스 필드(입력값 연결) / 고정값(직접 입력) / 자동생성(채번 규칙)</p>
|
||||||
💡 소스 필드가 없으면 정적 값이 사용됩니다.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -671,9 +671,11 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
||||||
console.log("🗑️ 품목 삭제:", deletedItem);
|
console.log("🗑️ 품목 삭제:", deletedItem);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// screenId 전달하여 제어관리 실행 가능하도록 함
|
||||||
const response = await dynamicFormApi.deleteFormDataFromTable(
|
const response = await dynamicFormApi.deleteFormDataFromTable(
|
||||||
deletedItem.id,
|
deletedItem.id,
|
||||||
screenData.screenInfo.tableName,
|
screenData.screenInfo.tableName,
|
||||||
|
modalState.screenId || screenData.screenInfo?.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
|
|
||||||
|
|
@ -1676,7 +1676,8 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||||
try {
|
try {
|
||||||
// console.log("🗑️ 삭제 실행:", { recordId, tableName, formData });
|
// console.log("🗑️ 삭제 실행:", { recordId, tableName, formData });
|
||||||
|
|
||||||
const result = await dynamicFormApi.deleteFormDataFromTable(recordId, tableName);
|
// screenId 전달하여 제어관리 실행 가능하도록 함
|
||||||
|
const result = await dynamicFormApi.deleteFormDataFromTable(recordId, tableName, screenInfo?.id);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
alert("삭제되었습니다.");
|
alert("삭제되었습니다.");
|
||||||
|
|
|
||||||
|
|
@ -202,14 +202,19 @@ export class DynamicFormApi {
|
||||||
* 실제 테이블에서 폼 데이터 삭제
|
* 실제 테이블에서 폼 데이터 삭제
|
||||||
* @param id 레코드 ID
|
* @param id 레코드 ID
|
||||||
* @param tableName 테이블명
|
* @param tableName 테이블명
|
||||||
|
* @param screenId 화면 ID (제어관리 실행용, 선택사항)
|
||||||
* @returns 삭제 결과
|
* @returns 삭제 결과
|
||||||
*/
|
*/
|
||||||
static async deleteFormDataFromTable(id: string | number, tableName: string): Promise<ApiResponse<void>> {
|
static async deleteFormDataFromTable(
|
||||||
|
id: string | number,
|
||||||
|
tableName: string,
|
||||||
|
screenId?: number
|
||||||
|
): Promise<ApiResponse<void>> {
|
||||||
try {
|
try {
|
||||||
console.log("🗑️ 실제 테이블에서 폼 데이터 삭제 요청:", { id, tableName });
|
console.log("🗑️ 실제 테이블에서 폼 데이터 삭제 요청:", { id, tableName, screenId });
|
||||||
|
|
||||||
await apiClient.delete(`/dynamic-form/${id}`, {
|
await apiClient.delete(`/dynamic-form/${id}`, {
|
||||||
data: { tableName },
|
data: { tableName, screenId },
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("✅ 실제 테이블에서 폼 데이터 삭제 성공");
|
console.log("✅ 실제 테이블에서 폼 데이터 삭제 성공");
|
||||||
|
|
|
||||||
|
|
@ -967,11 +967,11 @@ export class ButtonActionExecutor {
|
||||||
deletedItemIds,
|
deletedItemIds,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 삭제 API 호출
|
// 삭제 API 호출 - screenId 전달하여 제어관리 실행 가능하도록 함
|
||||||
for (const itemId of deletedItemIds) {
|
for (const itemId of deletedItemIds) {
|
||||||
try {
|
try {
|
||||||
console.log(`🗑️ [handleSave] 항목 삭제 중: ${itemId} from ${targetTable}`);
|
console.log(`🗑️ [handleSave] 항목 삭제 중: ${itemId} from ${targetTable}`);
|
||||||
const deleteResult = await DynamicFormApi.deleteFormDataFromTable(itemId, targetTable);
|
const deleteResult = await DynamicFormApi.deleteFormDataFromTable(itemId, targetTable, context.screenId);
|
||||||
if (deleteResult.success) {
|
if (deleteResult.success) {
|
||||||
console.log(`✅ [handleSave] 항목 삭제 완료: ${itemId}`);
|
console.log(`✅ [handleSave] 항목 삭제 완료: ${itemId}`);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1967,7 +1967,8 @@ export class ButtonActionExecutor {
|
||||||
for (const deletedItem of deletedItems) {
|
for (const deletedItem of deletedItems) {
|
||||||
console.log(`🗑️ [DELETE] 품목 삭제: id=${deletedItem.id}, tableName=${saveTableName}`);
|
console.log(`🗑️ [DELETE] 품목 삭제: id=${deletedItem.id}, tableName=${saveTableName}`);
|
||||||
|
|
||||||
const deleteResult = await DynamicFormApi.deleteFormDataFromTable(deletedItem.id, saveTableName);
|
// screenId 전달하여 제어관리 실행 가능하도록 함
|
||||||
|
const deleteResult = await DynamicFormApi.deleteFormDataFromTable(deletedItem.id, saveTableName, context.screenId);
|
||||||
|
|
||||||
if (!deleteResult.success) {
|
if (!deleteResult.success) {
|
||||||
throw new Error(deleteResult.message || "품목 삭제 실패");
|
throw new Error(deleteResult.message || "품목 삭제 실패");
|
||||||
|
|
@ -2434,7 +2435,8 @@ export class ButtonActionExecutor {
|
||||||
if (deleteId) {
|
if (deleteId) {
|
||||||
console.log("다중 데이터 삭제:", { tableName, screenId, id: deleteId });
|
console.log("다중 데이터 삭제:", { tableName, screenId, id: deleteId });
|
||||||
|
|
||||||
const deleteResult = await DynamicFormApi.deleteFormDataFromTable(deleteId, tableName);
|
// screenId 전달하여 제어관리 실행 가능하도록 함
|
||||||
|
const deleteResult = await DynamicFormApi.deleteFormDataFromTable(deleteId, tableName, screenId);
|
||||||
if (!deleteResult.success) {
|
if (!deleteResult.success) {
|
||||||
throw new Error(`ID ${deleteId} 삭제 실패: ${deleteResult.message}`);
|
throw new Error(`ID ${deleteId} 삭제 실패: ${deleteResult.message}`);
|
||||||
}
|
}
|
||||||
|
|
@ -2469,8 +2471,8 @@ export class ButtonActionExecutor {
|
||||||
if (tableName && screenId && formData.id) {
|
if (tableName && screenId && formData.id) {
|
||||||
console.log("단일 데이터 삭제:", { tableName, screenId, id: formData.id });
|
console.log("단일 데이터 삭제:", { tableName, screenId, id: formData.id });
|
||||||
|
|
||||||
// 실제 삭제 API 호출
|
// 실제 삭제 API 호출 - screenId 전달하여 제어관리 실행 가능하도록 함
|
||||||
const deleteResult = await DynamicFormApi.deleteFormDataFromTable(formData.id, tableName);
|
const deleteResult = await DynamicFormApi.deleteFormDataFromTable(formData.id, tableName, screenId);
|
||||||
|
|
||||||
if (!deleteResult.success) {
|
if (!deleteResult.success) {
|
||||||
throw new Error(deleteResult.message || "삭제에 실패했습니다.");
|
throw new Error(deleteResult.message || "삭제에 실패했습니다.");
|
||||||
|
|
@ -4251,7 +4253,8 @@ export class ButtonActionExecutor {
|
||||||
throw new Error("삭제할 항목의 ID를 찾을 수 없습니다.");
|
throw new Error("삭제할 항목의 ID를 찾을 수 없습니다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await DynamicFormApi.deleteFormDataFromTable(deleteId, context.tableName);
|
// screenId 전달하여 제어관리 실행 가능하도록 함
|
||||||
|
const result = await DynamicFormApi.deleteFormDataFromTable(deleteId, context.tableName, context.screenId);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
console.log("✅ 삭제 성공:", result);
|
console.log("✅ 삭제 성공:", result);
|
||||||
|
|
|
||||||
|
|
@ -344,6 +344,11 @@ export interface InsertActionNodeData {
|
||||||
targetField: string;
|
targetField: string;
|
||||||
targetFieldLabel?: string;
|
targetFieldLabel?: string;
|
||||||
staticValue?: any;
|
staticValue?: any;
|
||||||
|
// 🔥 값 생성 유형 추가
|
||||||
|
valueType?: "source" | "static" | "autoGenerate"; // 소스 필드 / 고정값 / 자동 생성
|
||||||
|
// 자동 생성 옵션 (valueType === "autoGenerate" 일 때)
|
||||||
|
numberingRuleId?: string; // 채번 규칙 ID
|
||||||
|
numberingRuleName?: string; // 채번 규칙명 (표시용)
|
||||||
}>;
|
}>;
|
||||||
options: {
|
options: {
|
||||||
batchSize?: number;
|
batchSize?: number;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue