[agent-pipeline] pipe-20260329010534-qgv9 round-2

This commit is contained in:
DDD1542
2026-03-29 13:32:26 +09:00
parent 7418e712cd
commit 18d237c95e
35 changed files with 426 additions and 432 deletions
+17 -17
View File
@@ -61,8 +61,8 @@ export function CodeDetailPanel({ categoryCode }: CodeDetailPanelProps) {
// 코드 맵 생성
codes.forEach((code) => {
const codeValue = code.codeValue || code.code_value || "";
const parentValue = code.parentCodeValue || code.parent_code_value;
const codeValue = code.code_value || "";
const parentValue = code.parent_code_value;
codeMap.set(codeValue, code);
if (parentValue) {
@@ -77,14 +77,14 @@ export function CodeDetailPanel({ categoryCode }: CodeDetailPanelProps) {
const traverse = (parentValue: string | null, depth: number) => {
const children = parentValue
? childrenMap.get(parentValue) || []
: codes.filter((c) => !c.parentCodeValue && !c.parent_code_value);
: codes.filter((c) => !c.parent_code_value);
// 정렬 순서로 정렬
children
.sort((a, b) => (a.sortOrder || a.sort_order || 0) - (b.sortOrder || b.sort_order || 0))
.sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0))
.forEach((code) => {
result.push(code);
const codeValue = code.codeValue || code.code_value || "";
const codeValue = code.code_value || "";
traverse(codeValue, depth + 1);
});
};
@@ -118,7 +118,7 @@ export function CodeDetailPanel({ categoryCode }: CodeDetailPanelProps) {
const childrenMap = useMemo(() => {
const map = new Map<string, CodeInfo[]>();
codes.forEach((code) => {
const parentValue = code.parentCodeValue || code.parent_code_value;
const parentValue = code.parent_code_value;
if (parentValue) {
if (!map.has(parentValue)) {
map.set(parentValue, []);
@@ -144,14 +144,14 @@ export function CodeDetailPanel({ categoryCode }: CodeDetailPanelProps) {
// 특정 코드가 표시되어야 하는지 확인 (부모가 접혀있으면 숨김)
const isCodeVisible = (code: CodeInfo): boolean => {
const parentValue = code.parentCodeValue || code.parent_code_value;
const parentValue = code.parent_code_value;
if (!parentValue) return true; // 최상위 코드는 항상 표시
// 부모가 접혀있으면 숨김
if (collapsedCodes.has(parentValue)) return false;
// 부모의 부모도 확인 (재귀적으로)
const parentCode = codes.find((c) => (c.codeValue || c.code_value) === parentValue);
const parentCode = codes.find((c) => c.code_value === parentValue);
if (parentCode) {
return isCodeVisible(parentCode);
}
@@ -176,7 +176,7 @@ export function CodeDetailPanel({ categoryCode }: CodeDetailPanelProps) {
})),
});
},
getItemId: (code: CodeInfo) => code.codeValue || code.code_value,
getItemId: (code: CodeInfo) => code.code_value,
});
// 새 코드 생성
@@ -196,7 +196,7 @@ export function CodeDetailPanel({ categoryCode }: CodeDetailPanelProps) {
// 하위 코드 추가
const handleAddChild = (parentCode: CodeInfo) => {
setEditingCode(null);
setDefaultParentCode(parentCode.codeValue || parentCode.code_value || "");
setDefaultParentCode(parentCode.code_value || "");
setShowFormModal(true);
};
@@ -213,7 +213,7 @@ export function CodeDetailPanel({ categoryCode }: CodeDetailPanelProps) {
try {
await deleteCodeMutation.mutateAsync({
categoryCode,
codeValue: deletingCode.codeValue || deletingCode.code_value,
codeValue: deletingCode.code_value,
});
setShowDeleteModal(false);
@@ -298,11 +298,11 @@ export function CodeDetailPanel({ categoryCode }: CodeDetailPanelProps) {
<>
<DndContext {...dragAndDrop.dndContextProps}>
<SortableContext
items={visibleCodes.map((code) => code.codeValue || code.code_value)}
items={visibleCodes.map((code) => code.code_value)}
strategy={verticalListSortingStrategy}
>
{visibleCodes.map((code, index) => {
const codeValue = code.codeValue || code.code_value || "";
const codeValue = code.code_value || "";
const children = childrenMap.get(codeValue) || [];
const hasChildren = children.length > 0;
const isExpanded = !collapsedCodes.has(codeValue);
@@ -334,17 +334,17 @@ export function CodeDetailPanel({ categoryCode }: CodeDetailPanelProps) {
<div className="flex items-start justify-between">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<h4 className="text-sm font-semibold">{activeCode.codeName || activeCode.code_name}</h4>
<h4 className="text-sm font-semibold">{activeCode.code_name}</h4>
<Badge
variant={
activeCode.isActive === "Y" || activeCode.is_active === "Y" ? "default" : "secondary"
activeCode.is_active === "Y" ? "default" : "secondary"
}
>
{activeCode.isActive === "Y" || activeCode.is_active === "Y" ? "활성" : "비활성"}
{activeCode.is_active === "Y" ? "활성" : "비활성"}
</Badge>
</div>
<p className="text-muted-foreground mt-1 text-xs">
{activeCode.codeValue || activeCode.code_value}
{activeCode.code_value}
</p>
{activeCode.description && (
<p className="text-muted-foreground mt-1 text-xs">{activeCode.description}</p>
@@ -1272,7 +1272,7 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
}
} else if (uploadMode === "insert" || uploadMode === "upsert") {
// 신규 등록 (insert, upsert 모드)
const formData = { screenId: 0, tableName, data: dataToSave };
const formData = { screen_id: 0, table_name: tableName, data: dataToSave };
console.log(`📝 [행 ${rowIdx + 1}] 신규 등록 시도 (mode: ${uploadMode}):`, dataToSave);
const result = await DynamicFormApi.saveFormData(formData);
if (result.success) {
+63 -63
View File
@@ -167,14 +167,14 @@ export default function CopyScreenModal({
if (isOpen && mode === "screen" && sourceScreen) {
// 단일 화면 복제 모드
setScreenName(`${sourceScreen.screen_name ?? sourceScreen.screenName} (복사본)`);
setScreenName(`${sourceScreen.screen_name} (복사본)`);
setDescription(sourceScreen.description || "");
// 대상 회사 코드 설정
if (isSuperAdmin) {
setTargetCompanyCode(sourceScreen.company_code ?? sourceScreen.companyCode);
setTargetCompanyCode(sourceScreen.company_code);
} else {
setTargetCompanyCode(sourceScreen.company_code ?? sourceScreen.companyCode);
setTargetCompanyCode(sourceScreen.company_code);
}
// 대상 그룹 초기화 (전달받은 값 또는 null)
@@ -230,7 +230,7 @@ export default function CopyScreenModal({
if (useBulkRename) {
// 일괄 수정 사용 시: (복사본) 텍스트 제거
const newMainName = applyBulkRename(sourceScreen.screen_name ?? sourceScreen.screenName);
const newMainName = applyBulkRename(sourceScreen.screen_name);
setScreenName(newMainName);
// 모달 화면명 업데이트
@@ -242,8 +242,8 @@ export default function CopyScreenModal({
);
} else {
// 일괄 수정 미사용 시: (복사본) 텍스트 추가
setScreenName(`${sourceScreen.screen_name ?? sourceScreen.screenName} (복사본)`);
setScreenName(`${sourceScreen.screen_name} (복사본)`);
setLinkedScreens((prev) =>
prev.map((screen) => ({
...screen,
@@ -259,7 +259,7 @@ export default function CopyScreenModal({
const sourceCompanyCode = mode === "group"
? sourceGroup?.company_code
: sourceScreen?.company_code ?? sourceScreen?.companyCode;
: sourceScreen?.company_code;
// 원본 회사와 같은 회사가 선택되어 있으면 다른 회사로 변경
if (sourceCompanyCode && targetCompanyCode === sourceCompanyCode) {
@@ -317,8 +317,8 @@ export default function CopyScreenModal({
const data = response.data.data || response.data || [];
console.log("📋 회사 목록 데이터:", data);
const mappedCompanies = data.map((c: any) => ({
companyCode: c.company_code || c.companyCode,
companyName: c.company_name || c.companyName,
companyCode: c.company_code,
companyName: c.company_name,
}));
console.log("📋 매핑된 회사 목록:", mappedCompanies);
setCompanies(mappedCompanies);
@@ -336,8 +336,8 @@ export default function CopyScreenModal({
try {
setLoadingLinkedScreens(true);
console.log("📡 API 호출: detectLinkedModals", sourceScreen.screen_id ?? sourceScreen.screenId);
const linked = await screenApi.detectLinkedModals(sourceScreen.screen_id ?? sourceScreen.screenId);
console.log("📡 API 호출: detectLinkedModals", sourceScreen.screen_id);
const linked = await screenApi.detectLinkedModals(sourceScreen.screen_id);
console.log("✅ 연결된 모달 화면 감지 결과:", linked);
// 초기 newScreenName 설정
@@ -444,8 +444,8 @@ export default function CopyScreenModal({
return {
main: {
original: sourceScreen.screen_name ?? sourceScreen.screenName,
preview: applyBulkRename(sourceScreen.screen_name ?? sourceScreen.screenName), // (복사본) 없음
original: sourceScreen.screen_name,
preview: applyBulkRename(sourceScreen.screen_name), // (복사본) 없음
},
modals: linkedScreens.map((screen) => ({
original: screen.screenName,
@@ -561,7 +561,7 @@ export default function CopyScreenModal({
setIsCopying(true);
// 화면명 중복 체크
const companyCode = targetCompanyCode || sourceScreen.company_code || sourceScreen.companyCode;
const companyCode = targetCompanyCode || sourceScreen.company_code;
// 메인 화면명 중복 체크
const isMainDuplicate = await screenApi.checkDuplicateScreenName(
@@ -591,7 +591,7 @@ export default function CopyScreenModal({
}
// 메인 화면 + 모달 화면들 일괄 복사
const result = await screenApi.copyScreenWithModals(sourceScreen.screen_id ?? sourceScreen.screenId, {
const result = await screenApi.copyScreenWithModals(sourceScreen.screen_id, {
targetCompanyCode: targetCompanyCode || undefined, // 최고 관리자: 대상 회사 전달
mainScreen: {
screenName: screenName.trim(),
@@ -608,14 +608,14 @@ export default function CopyScreenModal({
console.log("✅ 복사 완료:", result);
// 대상 그룹이 선택된 경우 복제된 메인 화면을 그룹에 추가
if (selectedTargetGroupId && (result.mainScreen?.screen_id ?? result.mainScreen?.screenId)) {
if (selectedTargetGroupId && result.mainScreen?.screen_id) {
try {
await addScreenToGroup({
group_id: selectedTargetGroupId,
screen_id: result.mainScreen.screen_id ?? result.mainScreen.screenId,
screen_id: result.mainScreen.screen_id,
screen_role: "MAIN",
display_order: 1,
target_company_code: targetCompanyCode || sourceScreen.company_code || sourceScreen.companyCode, // 대상 회사 코드 전달
target_company_code: targetCompanyCode || sourceScreen.company_code, // 대상 회사 코드 전달
});
console.log(`✅ 복제된 화면을 그룹(${selectedTargetGroupId})에 추가 완료`);
} catch (groupError) {
@@ -625,7 +625,7 @@ export default function CopyScreenModal({
}
// 추가 복사 옵션 처리 (단일 화면 복제용)
const sourceCompanyCode = sourceScreen.company_code ?? sourceScreen.companyCode;
const sourceCompanyCode = sourceScreen.company_code;
const copyTargetCompanyCode = targetCompanyCode || sourceCompanyCode;
let additionalCopyMessages: string[] = [];
@@ -634,8 +634,8 @@ export default function CopyScreenModal({
try {
console.log("📋 단일 화면: 채번규칙 복제 시작...");
const numberingResult = await apiClient.post("/api/screen-management/copy-numbering-rules", {
sourceCompanyCode,
targetCompanyCode: copyTargetCompanyCode
source_company_code: sourceCompanyCode,
target_company_code: copyTargetCompanyCode
});
if (numberingResult.data.success) {
additionalCopyMessages.push(`채번규칙 ${numberingResult.data.copiedCount || 0}`);
@@ -651,8 +651,8 @@ export default function CopyScreenModal({
try {
console.log("📋 단일 화면: 카테고리 값 복제 시작...");
const categoryResult = await apiClient.post("/api/screen-management/copy-category-mapping", {
sourceCompanyCode,
targetCompanyCode: copyTargetCompanyCode
source_company_code: sourceCompanyCode,
target_company_code: copyTargetCompanyCode
});
if (categoryResult.data.success) {
additionalCopyMessages.push(`카테고리 값 ${categoryResult.data.copiedValues || 0}`);
@@ -668,8 +668,8 @@ export default function CopyScreenModal({
try {
console.log("📋 단일 화면: 테이블 타입 컬럼 복제 시작...");
const tableTypeResult = await apiClient.post("/api/screen-management/copy-table-type-columns", {
sourceCompanyCode,
targetCompanyCode: copyTargetCompanyCode
source_company_code: sourceCompanyCode,
target_company_code: copyTargetCompanyCode
});
if (tableTypeResult.data.success) {
additionalCopyMessages.push(`테이블 타입 컬럼 ${tableTypeResult.data.copiedCount || 0}`);
@@ -771,7 +771,7 @@ export default function CopyScreenModal({
const tableName = typeof s === 'object' ? s.table_name : '';
// allScreens에서 먼저 찾고, 없으면 그룹의 screens 정보로 대체
let screenData = allScreens.find((sc) => (sc.screen_id ?? sc.screenId) === screenId);
let screenData = allScreens.find((sc) => sc.screen_id === screenId);
if (!screenData && screenId && screenName) {
// allScreens에 없는 경우 (다른 회사 화면) - 그룹의 screens 정보로 최소한의 데이터 구성
screenData = {
@@ -799,13 +799,13 @@ export default function CopyScreenModal({
setCopyProgress({
current: stats.screens + 1,
total: totalScreenCount,
message: `화면 복제 중: ${screen.screen_name ?? screen.screenName}`
message: `화면 복제 중: ${screen.screen_name}`
});
const transformedScreenName = transformName(screen.screen_name ?? screen.screenName, false, sourceGroupData.company_code);
console.log(` 📄 화면 복제: ${screen.screen_name ?? screen.screenName}${transformedScreenName}`);
const transformedScreenName = transformName(screen.screen_name, false, sourceGroupData.company_code);
console.log(` 📄 화면 복제: ${screen.screen_name}${transformedScreenName}`);
const result = await screenApi.copyScreenWithModals(screen.screen_id ?? screen.screenId, {
const result = await screenApi.copyScreenWithModals(screen.screen_id, {
targetCompanyCode: targetCompany,
mainScreen: {
screenName: transformedScreenName, // 일괄 이름 변경 적용
@@ -815,22 +815,22 @@ export default function CopyScreenModal({
modalScreens: [],
});
if (result.mainScreen?.screen_id ?? result.mainScreen?.screenId) {
if (result.mainScreen?.screen_id) {
// 원본 화면 ID -> 새 화면 ID 매핑 기록
screenIdMap[screen.screen_id ?? screen.screenId] = result.mainScreen.screen_id ?? result.mainScreen.screenId;
screenIdMap[screen.screen_id] = result.mainScreen.screen_id;
await addScreenToGroup({
group_id: newGroup.id,
screen_id: result.mainScreen.screen_id ?? result.mainScreen.screenId,
screen_id: result.mainScreen.screen_id,
screen_role: screenRole || "MAIN",
display_order: displayOrder, // 원본 정렬순서 유지
target_company_code: targetCompany, // 대상 회사 코드 전달
});
stats.screens++;
console.log(` ✅ 화면 복제 완료: ${result.mainScreen.screen_name ?? result.mainScreen.screenName} (${screen.screen_id ?? screen.screenId}${result.mainScreen.screen_id ?? result.mainScreen.screenId})`);
console.log(` ✅ 화면 복제 완료: ${result.mainScreen.screen_name} (${screen.screen_id}${result.mainScreen.screen_id})`);
}
} catch (screenError) {
console.error(` ❌ 화면 복제 실패 (${screen.screen_code ?? screen.screenCode}):`, screenError);
console.error(` ❌ 화면 복제 실패 (${screen.screen_code}):`, screenError);
}
}
}
@@ -951,7 +951,7 @@ export default function CopyScreenModal({
const tableName = typeof s === 'object' ? s.table_name : '';
// allScreens에서 먼저 찾고, 없으면 그룹의 screens 정보로 대체
let screenData = allScreens.find((sc) => (sc.screen_id ?? sc.screenId) === screenId);
let screenData = allScreens.find((sc) => sc.screen_id === screenId);
const foundInAllScreens = !!screenData;
if (!screenData && screenId && screenName) {
@@ -966,7 +966,7 @@ export default function CopyScreenModal({
companyCode: sourceGroup.company_code || '',
} as any;
} else if (screenData) {
console.log(` ✅ allScreens에서 찾음: ${screenId} - ${screenData.screen_name ?? screenData.screenName}`);
console.log(` ✅ allScreens에서 찾음: ${screenId} - ${screenData.screen_name}`);
} else {
console.log(` ❌ 화면 정보 없음: screenId=${screenId}, screenName=${screenName}`);
}
@@ -974,7 +974,7 @@ export default function CopyScreenModal({
}).filter(item => item.screenData && item.screenId); // screenId가 유효한 것만
console.log(`🔍 매핑 완료: ${screensWithOrder.length}개 화면 복사 예정`);
screensWithOrder.forEach(item => console.log(` - ${item.screenId}: ${item.screenData?.screenName}`));
screensWithOrder.forEach(item => console.log(` - ${item.screenId}: ${item.screenData?.screen_name}`));
// display_order 순으로 정렬
screensWithOrder.sort((a, b) => (a.displayOrder || 0) - (b.displayOrder || 0));
@@ -989,13 +989,13 @@ export default function CopyScreenModal({
setCopyProgress({
current: stats.screens + 1,
total: totalScreenCount,
message: `화면 복제 중: ${screen.screen_name ?? screen.screenName}`
message: `화면 복제 중: ${screen.screen_name}`
});
const transformedScreenName = transformName(screen.screen_name ?? screen.screenName, false, sourceGroup.company_code);
console.log(`📄 화면 복제: ${screen.screen_name ?? screen.screenName}${transformedScreenName}`);
const transformedScreenName = transformName(screen.screen_name, false, sourceGroup.company_code);
console.log(`📄 화면 복제: ${screen.screen_name}${transformedScreenName}`);
const result = await screenApi.copyScreenWithModals(screen.screen_id ?? screen.screenId, {
const result = await screenApi.copyScreenWithModals(screen.screen_id, {
targetCompanyCode: finalCompanyCode,
mainScreen: {
screenName: transformedScreenName, // 일괄 이름 변경 적용
@@ -1005,22 +1005,22 @@ export default function CopyScreenModal({
modalScreens: [],
});
if (result.mainScreen?.screen_id ?? result.mainScreen?.screenId) {
if (result.mainScreen?.screen_id) {
// 원본 화면 ID -> 새 화면 ID 매핑 기록
screenIdMap[screen.screen_id ?? screen.screenId] = result.mainScreen.screen_id ?? result.mainScreen.screenId;
screenIdMap[screen.screen_id] = result.mainScreen.screen_id;
await addScreenToGroup({
group_id: newRootGroup.id,
screen_id: result.mainScreen.screen_id ?? result.mainScreen.screenId,
screen_id: result.mainScreen.screen_id,
screen_role: screenRole || "MAIN",
display_order: displayOrder, // 원본 정렬순서 유지
target_company_code: finalCompanyCode, // 대상 회사 코드 전달
});
stats.screens++;
console.log(`✅ 화면 복제 완료: ${result.mainScreen.screen_name ?? result.mainScreen.screenName} (${screen.screen_id ?? screen.screenId}${result.mainScreen.screen_id ?? result.mainScreen.screenId})`);
console.log(`✅ 화면 복제 완료: ${result.mainScreen.screen_name} (${screen.screen_id}${result.mainScreen.screen_id})`);
}
} catch (screenError) {
console.error(`화면 복제 실패 (${screen.screen_code ?? screen.screenCode}):`, screenError);
console.error(`화면 복제 실패 (${screen.screen_code}):`, screenError);
}
}
}
@@ -1068,7 +1068,7 @@ export default function CopyScreenModal({
// 7-1. 메뉴 동기화 (화면 그룹 → 메뉴) - 항상 실행
const syncResponse = await apiClient.post("/screen-groups/sync/screen-to-menu", {
targetCompanyCode: finalCompanyCode,
target_company_code: finalCompanyCode,
});
if (syncResponse.data?.success) {
@@ -1079,9 +1079,9 @@ export default function CopyScreenModal({
console.log("📋 화면-메뉴 할당 복제 시작...");
const menuAssignResponse = await apiClient.post("/screen-management/copy-menu-assignments", {
sourceCompanyCode: sourceGroup.company_code,
targetCompanyCode: finalCompanyCode,
screenIdMap,
source_company_code: sourceGroup.company_code,
target_company_code: finalCompanyCode,
screen_id_map: screenIdMap,
});
if (menuAssignResponse.data?.success) {
@@ -1096,8 +1096,8 @@ export default function CopyScreenModal({
console.log("📋 채번규칙 복제 시작...");
const numberingResponse = await apiClient.post("/numbering-rules/copy-for-company", {
sourceCompanyCode: sourceGroup.company_code,
targetCompanyCode: finalCompanyCode,
source_company_code: sourceGroup.company_code,
target_company_code: finalCompanyCode,
});
if (numberingResponse.data?.success) {
@@ -1124,8 +1124,8 @@ export default function CopyScreenModal({
console.log("📋 카테고리 값 복제 시작...");
const response = await apiClient.post("/screen-management/copy-category-mapping", {
sourceCompanyCode: sourceGroup.company_code,
targetCompanyCode: finalCompanyCode,
source_company_code: sourceGroup.company_code,
target_company_code: finalCompanyCode,
});
if (response.data?.success) {
@@ -1148,8 +1148,8 @@ export default function CopyScreenModal({
console.log("📋 테이블 타입 컬럼 복제 시작...");
const response = await apiClient.post("/screen-management/copy-table-type-columns", {
sourceCompanyCode: sourceGroup.company_code,
targetCompanyCode: finalCompanyCode,
source_company_code: sourceGroup.company_code,
target_company_code: finalCompanyCode,
});
if (response.data?.success) {
@@ -1169,9 +1169,9 @@ export default function CopyScreenModal({
try {
await apiClient.post("/audit-log", {
action: "COPY",
resourceType: "SCREEN",
resourceId: String(sourceGroup.id),
resourceName: sourceGroup.group_name,
resource_type: "SCREEN",
resource_id: String(sourceGroup.id),
resource_name: sourceGroup.group_name,
summary: `그룹 "${sourceGroup.group_name}" → "${rootGroupName}" 복제 (그룹 ${stats.groups}개, 화면 ${stats.screens}개)${finalCompanyCode !== sourceGroup.company_code ? ` [${sourceGroup.company_code}${finalCompanyCode}]` : ""}`,
changes: {
after: {
@@ -1663,7 +1663,7 @@ export default function CopyScreenModal({
<DialogHeader>
<DialogTitle className="text-base sm:text-lg"> </DialogTitle>
<DialogDescription className="text-xs sm:text-sm">
"{sourceScreen?.screen_name ?? sourceScreen?.screenName}" .
"{sourceScreen?.screen_name}" .
{linkedScreens.length > 0 && ` (모달 ${linkedScreens.length}개 포함)`}
</DialogDescription>
</DialogHeader>
@@ -1758,7 +1758,7 @@ export default function CopyScreenModal({
</SelectTrigger>
<SelectContent>
{companies
.filter((company) => company.companyCode !== (sourceScreen?.company_code ?? sourceScreen?.companyCode))
.filter((company) => company.companyCode !== sourceScreen?.company_code)
.map((company) => (
<SelectItem key={company.companyCode} value={company.companyCode}>
{company.companyName}
@@ -1768,7 +1768,7 @@ export default function CopyScreenModal({
</Select>
{sourceScreen && (
<p className="mt-1 text-[10px] text-amber-600">
* ({sourceScreen.company_code ?? sourceScreen.companyCode})
* ({sourceScreen.company_code})
</p>
)}
</div>
+5 -5
View File
@@ -948,8 +948,8 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
try {
const response = await dynamicFormApi.saveFormData({
screenId: modalState.screenId || 0,
tableName: screenData.screenInfo.table_name,
screen_id: modalState.screenId || 0,
table_name: screenData.screenInfo.table_name,
data: insertData,
});
@@ -1332,8 +1332,8 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
console.log("[EditModal] 최종 저장 데이터:", masterDataToSave);
const response = await dynamicFormApi.saveFormData({
screenId: modalState.screenId!,
tableName: screenData.screenInfo.table_name,
screen_id: modalState.screenId!,
table_name: screenData.screenInfo.table_name,
data: masterDataToSave,
});
@@ -1470,7 +1470,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
});
const response = await dynamicFormApi.updateFormData(recordId, {
tableName: screenData.screenInfo.table_name,
table_name: screenData.screenInfo.table_name,
data: dataToSave,
});
@@ -464,7 +464,7 @@ export const EnhancedInteractiveScreenViewer: React.FC<EnhancedInteractiveScreen
<CardContent className="space-y-2">
<div className="flex items-center gap-2">
<Badge variant="outline"></Badge>
<span className="text-sm">{screenInfo.tableName}</span>
<span className="text-sm">{screenInfo.table_name}</span>
</div>
<div className="flex items-center gap-2">
<Badge variant="outline"></Badge>
@@ -162,24 +162,24 @@ export const FileAttachmentDetailModal: React.FC<FileAttachmentDetailModalProps>
if (response.success && response.data) {
const newFiles: FileInfo[] = response.data.map((file: any) => ({
objid: file.objid || `temp_${Date.now()}_${Math.random()}`,
savedFileName: file.saved_file_name || file.savedFileName,
realFileName: file.real_file_name || file.realFileName,
fileSize: file.file_size || file.fileSize,
fileExt: file.file_ext || file.fileExt,
filePath: file.file_path || file.filePath,
savedFileName: file.saved_file_name,
realFileName: file.real_file_name,
fileSize: file.file_size,
fileExt: file.file_ext,
filePath: file.file_path,
docType: fileConfig.docType,
docTypeName: fileConfig.docTypeName,
targetObjid: file.target_objid || file.targetObjid || recordId || screenId || '',
parentTargetObjid: file.parent_target_objid || file.parentTargetObjid,
companyCode: file.company_code || file.companyCode || 'DEFAULT',
targetObjid: file.target_objid || recordId || screenId || '',
parentTargetObjid: file.parent_target_objid,
companyCode: file.company_code || 'DEFAULT',
writer: file.writer || 'user',
regdate: file.regdate || new Date().toISOString(),
status: file.status || 'ACTIVE',
// 호환성 속성들
path: file.file_path || file.filePath,
name: file.real_file_name || file.realFileName,
path: file.file_path,
name: file.real_file_name,
id: file.objid,
size: file.file_size || file.fileSize,
size: file.file_size,
type: fileConfig.docType,
uploadedAt: file.regdate || new Date().toISOString(),
}));
@@ -879,16 +879,16 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
}
// 없으면 테이블 타입 관리에서 설정된 값 찾기
const tableColumn = tableColumns.find((col) => col.columnName === columnName);
const tableColumn = tableColumns.find((col) => col.column_name === columnName);
// input_type 우선 사용 (category 등)
const inputType = (tableColumn as any)?.input_type || (tableColumn as any)?.inputType;
const inputType = tableColumn?.input_type;
if (inputType) {
return inputType;
}
// 없으면 webType 사용
return tableColumn?.webType || "text";
// 없으면 web_type 사용
return tableColumn?.web_type || "text";
},
[component.columns, tableColumns],
);
@@ -903,9 +903,9 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
}
// 없으면 테이블 타입 관리에서 설정된 값 찾기
const tableColumn = tableColumns.find((col) => col.columnName === columnName);
const tableColumn = tableColumns.find((col) => col.column_name === columnName);
try {
return tableColumn?.detailSettings ? JSON.parse(tableColumn.detailSettings) : {};
return tableColumn?.detail_settings ? JSON.parse(tableColumn.detail_settings) : {};
} catch {
return {};
}
@@ -1009,13 +1009,13 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
setTableColumns(columns);
// 🆕 전체 컬럼 목록 설정
const columnNames = columns.map((col) => col.columnName);
const columnNames = columns.map((col) => col.column_name);
setAllAvailableColumns(columnNames);
// 🆕 컬럼명 -> 라벨 매핑 생성
const labels: Record<string, string> = {};
columns.forEach((col) => {
labels[col.columnName] = col.display_name || col.columnName;
labels[col.column_name] = col.display_name || col.column_name;
});
setColumnLabels(labels);
@@ -410,17 +410,17 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
console.log("📊 팝업 화면 로드 완료:", {
componentsCount: layout.components?.length || 0,
screenInfo: {
screenId: screen.screenId,
tableName: screen.tableName
screenId: screen.screen_id,
tableName: screen.table_name
},
popupFormData: {}
});
setPopupLayout(layout.components || []);
setPopupScreenResolution(layout.screenResolution || null);
setPopupScreenResolution(layout.screen_resolution || null);
setPopupScreenInfo({
id: popupScreen.screenId,
tableName: screen.tableName
tableName: screen.table_name
});
// 팝업 formData 초기화
@@ -1778,8 +1778,8 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
});
const saveData: DynamicFormData = {
screenId: screenInfo.id,
tableName: tableName,
screen_id: screenInfo.id,
table_name: tableName,
data: masterDataWithUserInfo,
};
@@ -320,12 +320,12 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
const screenData = response.data;
setPopupLayout(screenData.components || []);
setPopupScreenResolution({
width: screenData.screenResolution?.width || 1200,
height: screenData.screenResolution?.height || 800,
width: screenData.screen_resolution?.width || 1200,
height: screenData.screen_resolution?.height || 800,
});
setPopupScreenInfo({
id: screenData.id,
tableName: screenData.tableName,
tableName: screenData.table_name,
});
} else {
toast.error("팝업 화면을 불러올 수 없습니다.");
@@ -646,7 +646,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
});
const saveData: DynamicFormData = {
tableName: screenInfo.tableName,
table_name: screenInfo.tableName,
data: masterFormData,
};
@@ -750,7 +750,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
);
if (columnsResponse.data?.success && columnsResponse.data?.data) {
const columnsData = columnsResponse.data.data.columns || columnsResponse.data.data;
targetTableColumns = columnsData.map((col: any) => col.columnName || col.column_name || col.name);
targetTableColumns = columnsData.map((col: any) => col.column_name || col.name);
console.log("📍 대상 테이블 컬럼 목록:", targetTableColumns);
}
} catch (error) {
@@ -1033,7 +1033,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
isInteractive={true}
isDesignMode={false}
formData={{
screenId, // 🎯 화면 ID 전달
screen_id: screenId, // 🎯 화면 ID 전달
// 🎯 백엔드 API가 기대하는 정확한 형식으로 설정
autoLink: true, // 자동 연결 활성화
linkedTable: "screen_files", // 연결 테이블
+2 -2
View File
@@ -240,8 +240,8 @@ export const SaveModal: React.FC<SaveModalProps> = ({
const tableName = screenData.table_name || components.find((c) => c.columnName)?.table_name || "dynamic_form_data";
const saveData: DynamicFormData = {
screenId: screenId,
tableName: tableName,
screen_id: screenId,
table_name: tableName,
data: masterDataWithUserInfo,
};
+5 -5
View File
@@ -368,8 +368,8 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
if (response.success && response.data) {
// tableName과 displayName 매핑 (백엔드에서 displayName으로 라벨을 반환함)
const tableList = response.data.map((table: any) => ({
tableName: table.tableName,
tableLabel: table.displayName || table.tableName,
tableName: table.table_name,
tableLabel: table.display_name || table.table_name,
}));
setTables(tableList);
}
@@ -679,7 +679,7 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
setTimeout(async () => {
try {
// 화면 레이아웃 로드
const layoutData = await screenApi.getLayout(screen.screenId);
const layoutData = await screenApi.getLayout(screen.screen_id);
console.log("📊 미리보기 레이아웃 로드:", layoutData);
setPreviewLayout(layoutData);
} catch (error) {
@@ -1923,8 +1923,8 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
isSelected={false}
isDesignMode={false}
onClick={() => {}}
screenId={screenToPreview!.screenId}
tableName={screenToPreview?.tableName}
screenId={screenToPreview!.screen_id}
tableName={screenToPreview?.table_name}
formData={previewFormData}
onFormDataChange={(fieldName, value) => {
setPreviewFormData((prev) => ({
@@ -1365,10 +1365,10 @@ function TableColumnAccordion({
</Badge>
<span className={`text-[10px] font-medium ${hasJoinSetting ? "text-orange-700" : "text-muted-foreground"}`}>
{editingJoin && editingJoin.columnName === (selectedColumn?.columnName || editingField) ? "연결 편집" : (hasJoinSetting ? "연결 정보" : "연결 설정")}
{editingJoin && editingJoin.columnName === (selectedColumn?.column_name || editingField) ? "연결 편집" : (hasJoinSetting ? "연결 정보" : "연결 설정")}
</span>
</div>
{editingJoin && editingJoin.columnName === (selectedColumn?.columnName || editingField) ? (
{editingJoin && editingJoin.columnName === (selectedColumn?.column_name || editingField) ? (
<div className="flex gap-1">
<Button variant="ghost" size="sm" className="h-5 px-1.5 text-[10px] text-muted-foreground hover:text-foreground" onClick={() => setEditingJoin(null)}>
@@ -1383,10 +1383,10 @@ function TableColumnAccordion({
size="sm"
className={`h-5 px-1 text-[10px] ${hasJoinSetting ? "text-amber-600 hover:text-orange-800" : "text-muted-foreground hover:text-foreground"}`}
onClick={() => startEditingJoin(
selectedColumn?.columnName || editingField,
isJoinKey && joinRef ? joinRef.refTable : (selectedColumn?.referenceTable || ""),
isJoinKey && joinRef ? joinRef.refColumn : (selectedColumn?.referenceColumn || ""),
selectedColumn?.displayColumn || ""
selectedColumn?.column_name || editingField,
isJoinKey && joinRef ? joinRef.refTable : (selectedColumn?.reference_table || ""),
isJoinKey && joinRef ? joinRef.refColumn : (selectedColumn?.reference_column || ""),
selectedColumn?.display_column || ""
)}
>
<Settings2 className="h-3 w-3 mr-0.5" />
@@ -1395,7 +1395,7 @@ function TableColumnAccordion({
)}
</div>
{editingJoin && editingJoin.columnName === (selectedColumn?.columnName || editingField) ? (
{editingJoin && editingJoin.columnName === (selectedColumn?.column_name || editingField) ? (
<JoinSettingEditor
editingJoin={editingJoin}
setEditingJoin={setEditingJoin}
@@ -1410,11 +1410,11 @@ function TableColumnAccordion({
<div className="space-y-1.5 text-xs">
<div>
<span className="text-muted-foreground"> : </span>
<span className="font-mono text-orange-800">{isJoinKey && joinRef ? joinRef.refTable : selectedColumn?.referenceTable}</span>
<span className="font-mono text-orange-800">{isJoinKey && joinRef ? joinRef.refTable : selectedColumn?.reference_table}</span>
</div>
<div>
<span className="text-muted-foreground"> : </span>
<span className="font-mono text-orange-800">{isJoinKey && joinRef ? joinRef.refColumn : selectedColumn?.referenceColumn}</span>
<span className="font-mono text-orange-800">{isJoinKey && joinRef ? joinRef.refColumn : selectedColumn?.reference_column}</span>
</div>
</div>
) : (
@@ -1499,9 +1499,9 @@ function JoinSettingEditor({
const [refColSearchOpen, setRefColSearchOpen] = useState(false);
const [displayColSearchOpen, setDisplayColSearchOpen] = useState(false);
const selectedTable = allTables.find(t => t.tableName === editingJoin.referenceTable);
const selectedRefCol = refTableColumns.find(c => c.columnName === editingJoin.referenceColumn);
const selectedDisplayCol = refTableColumns.find(c => c.columnName === editingJoin.displayColumn);
const selectedTable = allTables.find(t => t.table_name === editingJoin.referenceTable);
const selectedRefCol = refTableColumns.find(c => c.column_name === editingJoin.referenceColumn);
const selectedDisplayCol = refTableColumns.find(c => c.column_name === editingJoin.displayColumn);
return (
<div className="space-y-2">
@@ -1515,7 +1515,7 @@ function JoinSettingEditor({
size="sm"
className="w-full justify-between h-7 text-xs font-normal"
>
{selectedTable?.displayName || editingJoin.referenceTable || "테이블 선택"}
{selectedTable?.display_name || editingJoin.referenceTable || "테이블 선택"}
<ChevronsUpDown className="h-3 w-3 opacity-50" />
</Button>
</PopoverTrigger>
@@ -1527,11 +1527,11 @@ function JoinSettingEditor({
<CommandGroup>
{allTables.map((t, idx) => (
<CommandItem
key={`${t.tableName}-${idx}`}
value={t.displayName || t.tableName}
key={`${t.table_name}-${idx}`}
value={t.display_name || t.table_name}
onSelect={() => {
setEditingJoin({ ...editingJoin, referenceTable: t.tableName, referenceColumn: "", displayColumn: "" });
loadRefTableColumns(t.tableName);
setEditingJoin({ ...editingJoin, referenceTable: t.table_name, referenceColumn: "", displayColumn: "" });
loadRefTableColumns(t.table_name);
setTableSearchOpen(false);
}}
className="text-xs"
@@ -1539,10 +1539,10 @@ function JoinSettingEditor({
<Check
className={cn(
"mr-2 h-3 w-3",
editingJoin.referenceTable === t.tableName ? "opacity-100" : "opacity-0"
editingJoin.referenceTable === t.table_name ? "opacity-100" : "opacity-0"
)}
/>
{t.displayName || t.tableName}
{t.display_name || t.table_name}
</CommandItem>
))}
</CommandGroup>
@@ -1563,7 +1563,7 @@ function JoinSettingEditor({
className="w-full justify-between h-7 text-xs font-normal"
disabled={!editingJoin.referenceTable || loadingRefColumns}
>
{loadingRefColumns ? "로딩중..." : (selectedRefCol?.displayName || editingJoin.referenceColumn || "컬럼 선택")}
{loadingRefColumns ? "로딩중..." : (selectedRefCol?.display_name || editingJoin.referenceColumn || "컬럼 선택")}
<ChevronsUpDown className="h-3 w-3 opacity-50" />
</Button>
</PopoverTrigger>
@@ -1575,10 +1575,10 @@ function JoinSettingEditor({
<CommandGroup>
{refTableColumns.map(c => (
<CommandItem
key={c.columnName}
value={c.displayName || c.columnName}
key={c.column_name}
value={c.display_name || c.column_name}
onSelect={() => {
setEditingJoin({ ...editingJoin, referenceColumn: c.columnName });
setEditingJoin({ ...editingJoin, referenceColumn: c.column_name });
setRefColSearchOpen(false);
}}
className="text-xs"
@@ -1586,10 +1586,10 @@ function JoinSettingEditor({
<Check
className={cn(
"mr-2 h-3 w-3",
editingJoin.referenceColumn === c.columnName ? "opacity-100" : "opacity-0"
editingJoin.referenceColumn === c.column_name ? "opacity-100" : "opacity-0"
)}
/>
{c.displayName || c.columnName}
{c.display_name || c.column_name}
</CommandItem>
))}
</CommandGroup>
@@ -1610,7 +1610,7 @@ function JoinSettingEditor({
className="w-full justify-between h-7 text-xs font-normal"
disabled={!editingJoin.referenceTable || loadingRefColumns}
>
{selectedDisplayCol?.displayName || editingJoin.displayColumn || "컬럼 선택"}
{selectedDisplayCol?.display_name || editingJoin.displayColumn || "컬럼 선택"}
<ChevronsUpDown className="h-3 w-3 opacity-50" />
</Button>
</PopoverTrigger>
@@ -1622,10 +1622,10 @@ function JoinSettingEditor({
<CommandGroup>
{refTableColumns.map(c => (
<CommandItem
key={c.columnName}
value={c.displayName || c.columnName}
key={c.column_name}
value={c.display_name || c.column_name}
onSelect={() => {
setEditingJoin({ ...editingJoin, displayColumn: c.columnName });
setEditingJoin({ ...editingJoin, displayColumn: c.column_name });
setDisplayColSearchOpen(false);
}}
className="text-xs"
@@ -1633,10 +1633,10 @@ function JoinSettingEditor({
<Check
className={cn(
"mr-2 h-3 w-3",
editingJoin.displayColumn === c.columnName ? "opacity-100" : "opacity-0"
editingJoin.displayColumn === c.column_name ? "opacity-100" : "opacity-0"
)}
/>
{c.displayName || c.columnName}
{c.display_name || c.column_name}
</CommandItem>
))}
</CommandGroup>
@@ -2627,7 +2627,7 @@ function FieldMappingTab({
const columnDisplayMap = useMemo(() => {
const map: Record<string, string> = {};
tableColumns.forEach((tc) => {
map[tc.columnName] = tc.displayName || tc.columnName;
map[tc.column_name] = tc.display_name || tc.column_name;
});
return map;
}, [tableColumns]);
@@ -2806,8 +2806,8 @@ function FieldMappingTab({
) : (
tableColumns.map((tableCol) => (
<CommandItem
key={tableCol.columnName}
value={tableCol.columnName}
key={tableCol.column_name}
value={tableCol.column_name}
onSelect={(value) => {
toast.info(`컬럼 변경: ${col}${value}`, {
description: "저장 기능은 아직 구현 중입니다."
@@ -2820,17 +2820,17 @@ function FieldMappingTab({
<div className="flex flex-col">
<span className={cn(
"font-medium",
tableCol.columnName === col && "text-primary"
tableCol.column_name === col && "text-primary"
)}>
{tableCol.displayName || tableCol.columnName}
{tableCol.display_name || tableCol.column_name}
</span>
{tableCol.displayName && tableCol.displayName !== tableCol.columnName && (
{tableCol.display_name && tableCol.display_name !== tableCol.column_name && (
<span className="text-[10px] text-muted-foreground font-mono">
{tableCol.columnName}
{tableCol.column_name}
</span>
)}
</div>
{tableCol.columnName === col && (
{tableCol.column_name === col && (
<Check className="ml-auto h-3 w-3 text-primary" />
)}
</CommandItem>
+1 -1
View File
@@ -40,7 +40,7 @@ export const dataApi = {
const page = raw.page ?? params?.page ?? 1;
const size = raw.size ?? params?.size ?? items.length;
const total = raw.total ?? items.length;
const totalPages = raw.total_pages ?? raw.totalPages ?? Math.max(1, Math.ceil(total / (size || 1)));
const totalPages = raw.total_pages ?? Math.max(1, Math.ceil(total / (size || 1)));
return { data: items, total, page, size, totalPages };
},
+6 -6
View File
@@ -85,11 +85,11 @@ export const loadDataflowRelationship = async (diagramId: number) => {
// 기존 구조를 redesigned 구조로 변환
relationshipsData = {
description: firstRelation.note || "",
connectionType: firstRelation.connectionType || "data_save",
connectionType: firstRelation.connection_type || firstRelation.connectionType || "data_save",
fromConnection: { id: 0, name: "메인 데이터베이스 (현재 시스템)" }, // 기본값
toConnection: { id: 0, name: "메인 데이터베이스 (현재 시스템)" }, // 기본값
fromTable: firstRelation.fromTable,
toTable: firstRelation.toTable,
fromTable: firstRelation.from_table || firstRelation.fromTable,
toTable: firstRelation.to_table || firstRelation.toTable,
actionType: "insert", // 기본값
controlConditions: [],
actionConditions: [],
@@ -108,9 +108,9 @@ export const loadDataflowRelationship = async (diagramId: number) => {
const plan = diagram.plan.find((p) => p.id === firstRelation.id);
if (plan && plan.actions && plan.actions.length > 0) {
const firstAction = plan.actions[0];
relationshipsData.actionType = firstAction.actionType || "insert";
relationshipsData.fieldMappings = firstAction.fieldMappings || [];
relationshipsData.actionConditions = firstAction.conditions || [];
relationshipsData.actionType = firstAction.action_type || firstAction.actionType || "insert";
relationshipsData.fieldMappings = firstAction.field_mappings || firstAction.fieldMappings || [];
relationshipsData.actionConditions = firstAction.action_conditions || firstAction.conditions || [];
}
}
}
+14 -14
View File
@@ -2,8 +2,8 @@ import { apiClient, ApiResponse } from "./client";
// 동적 폼 데이터 타입
export interface DynamicFormData {
screenId: number;
tableName: string;
screen_id?: number;
table_name: string;
data: Record<string, any>;
}
@@ -145,7 +145,7 @@ export class DynamicFormApi {
});
const response = await apiClient.patch(`/dynamic-form/${id}/partial`, {
tableName,
table_name: tableName,
originalData,
newData,
});
@@ -375,7 +375,7 @@ export class DynamicFormApi {
console.log("✅ 폼 데이터 검증 요청:", { tableName, data });
const response = await apiClient.post(`/dynamic-form/validate`, {
tableName,
table_name: tableName,
data,
});
@@ -440,8 +440,8 @@ export class DynamicFormApi {
filters?: Record<string, any>;
autoFilter?: {
enabled: boolean;
filterColumn?: string;
userField?: string;
filter_column?: string;
user_field?: string;
};
},
): Promise<ApiResponse<any[]>> {
@@ -455,8 +455,8 @@ export class DynamicFormApi {
size: params?.pageSize || params?.size || 100, // 기본값 100
autoFilter: params?.autoFilter ?? {
enabled: true,
filterColumn: "company_code",
userField: "company_code",
filter_column: "company_code",
user_field: "company_code",
},
};
@@ -675,7 +675,7 @@ export class DynamicFormApi {
masterFieldValues: Record<string, any>,
numberingRuleId?: string,
afterUploadFlowId?: string,
afterUploadFlows?: Array<{ flowId: string; order: number }>
afterUploadFlows?: Array<{ flow_id: string; order: number }>
): Promise<ApiResponse<MasterDetailSimpleUploadResult>> {
try {
console.log("📤 마스터-디테일 간단 모드 업로드:", {
@@ -688,11 +688,11 @@ export class DynamicFormApi {
const response = await apiClient.post(`/data/master-detail/upload-simple`, {
screen_id: screenId,
detailData,
masterFieldValues,
numberingRuleId,
afterUploadFlowId,
afterUploadFlows,
detail_data: detailData,
master_field_values: masterFieldValues,
numbering_rule_id: numberingRuleId,
after_upload_flow_id: afterUploadFlowId,
after_upload_flows: afterUploadFlows,
});
return {
+15 -15
View File
@@ -55,20 +55,20 @@ export const uploadFiles = async (params: {
// 추가 파라미터들 추가
if (params.tableName) formData.append("table_name", params.tableName);
if (params.fieldName) formData.append("fieldName", params.fieldName);
if (params.recordId) formData.append("recordId", String(params.recordId));
if (params.docType) formData.append("docType", params.docType);
if (params.docTypeName) formData.append("docTypeName", params.docTypeName);
if (params.targetObjid) formData.append("targetObjid", params.targetObjid);
if (params.parentTargetObjid) formData.append("parentTargetObjid", params.parentTargetObjid);
if (params.linkedTable) formData.append("linkedTable", params.linkedTable);
if (params.linkedField) formData.append("linkedField", params.linkedField);
if (params.autoLink !== undefined) formData.append("autoLink", params.autoLink.toString());
if (params.columnName) formData.append("columnName", params.columnName);
if (params.isVirtualFileColumn !== undefined) formData.append("isVirtualFileColumn", params.isVirtualFileColumn.toString());
if (params.fieldName) formData.append("field_name", params.fieldName);
if (params.recordId) formData.append("record_id", String(params.recordId));
if (params.docType) formData.append("doc_type", params.docType);
if (params.docTypeName) formData.append("doc_type_name", params.docTypeName);
if (params.targetObjid) formData.append("target_objid", params.targetObjid);
if (params.parentTargetObjid) formData.append("parent_target_objid", params.parentTargetObjid);
if (params.linkedTable) formData.append("linked_table", params.linkedTable);
if (params.linkedField) formData.append("linked_field", params.linkedField);
if (params.autoLink !== undefined) formData.append("auto_link", params.autoLink.toString());
if (params.columnName) formData.append("column_name", params.columnName);
if (params.isVirtualFileColumn !== undefined) formData.append("is_virtual_file_column", params.isVirtualFileColumn.toString());
if (params.companyCode) formData.append("company_code", params.companyCode); // 🔒 멀티테넌시
// 🆕 레코드 모드 플래그 추가 (백엔드에서 attachments 컬럼 자동 업데이트용)
if (params.isRecordMode !== undefined) formData.append("isRecordMode", params.isRecordMode.toString());
if (params.isRecordMode !== undefined) formData.append("is_record_mode", params.isRecordMode.toString());
const response = await apiClient.post("/files/upload", formData, {
headers: {
@@ -145,10 +145,10 @@ export const getFileInfo = async (fileId: string, serverFilename: string) => {
*/
export const getComponentFiles = async (params: {
screen_id: number;
componentId: string;
component_id: string;
table_name?: string;
recordId?: string;
columnName?: string;
record_id?: string;
column_name?: string;
}): Promise<{
success: boolean;
templateFiles: FileInfo[];
+12 -13
View File
@@ -12,13 +12,13 @@ export interface GetLayoutsParams {
page?: number;
size?: number;
category?: LayoutCategory;
layoutType?: LayoutType;
searchTerm?: string;
includePublic?: boolean;
layout_type?: LayoutType;
search_term?: string;
include_public?: boolean;
}
export interface DuplicateLayoutRequest {
newName: string;
new_name: string;
}
class LayoutApiService {
@@ -84,9 +84,9 @@ class LayoutApiService {
*/
async searchLayouts(
searchTerm: string,
params: Omit<GetLayoutsParams, "searchTerm"> = {},
params: Omit<GetLayoutsParams, "search_term"> = {},
): Promise<GetLayoutsResponse> {
return this.getLayouts({ ...params, searchTerm });
return this.getLayouts({ ...params, search_term: searchTerm });
}
/**
@@ -104,23 +104,23 @@ class LayoutApiService {
*/
async getLayoutsByType(
layoutType: LayoutType,
params: Omit<GetLayoutsParams, "layoutType"> = {},
params: Omit<GetLayoutsParams, "layout_type"> = {},
): Promise<GetLayoutsResponse> {
return this.getLayouts({ ...params, layoutType });
return this.getLayouts({ ...params, layout_type: layoutType });
}
/**
*
*/
async getPublicLayouts(params: Omit<GetLayoutsParams, "includePublic"> = {}): Promise<GetLayoutsResponse> {
return this.getLayouts({ ...params, includePublic: true });
async getPublicLayouts(params: Omit<GetLayoutsParams, "include_public"> = {}): Promise<GetLayoutsResponse> {
return this.getLayouts({ ...params, include_public: true });
}
/**
*
*/
async getPrivateLayouts(params: Omit<GetLayoutsParams, "includePublic"> = {}): Promise<GetLayoutsResponse> {
return this.getLayouts({ ...params, includePublic: false });
async getPrivateLayouts(params: Omit<GetLayoutsParams, "include_public"> = {}): Promise<GetLayoutsResponse> {
return this.getLayouts({ ...params, include_public: false });
}
}
@@ -129,4 +129,3 @@ export const layoutApi = new LayoutApiService();
// 기본 내보내기
export default layoutApi;
+1 -1
View File
@@ -347,7 +347,7 @@ export async function previewMailTemplate(
): Promise<{ html: string }> {
return fetchApi<{ html: string }>(`/mail/templates-file/${id}/preview`, {
method: 'POST',
data: { sampleData },
data: { sample_data: sampleData },
});
}
+4 -4
View File
@@ -316,10 +316,10 @@ export async function getSecondLevelMenus() {
const response = await apiClient.get<{
success: boolean;
data: Array<{
menuObjid: number;
menuName: string;
parentMenuName: string;
screenCode?: string;
menu_objid: number;
menu_name: string;
parent_menu_name: string;
screen_code?: string;
}>;
}>("/table-categories/second-level-menus");
return response.data;
@@ -58,7 +58,7 @@ export function ConditionalContainerConfigPanel({
const [screensLoading, setScreensLoading] = useState(false);
// 🆕 메뉴 기반 카테고리 관련 상태
const [availableMenus, setAvailableMenus] = useState<Array<{ menuObjid: number; menuName: string; parentMenuName: string; screenCode?: string }>>([]);
const [availableMenus, setAvailableMenus] = useState<Array<{ menu_objid: number; menu_name: string; parent_menu_name: string; screen_code?: string }>>([]);
const [menusLoading, setMenusLoading] = useState(false);
const [selectedMenuObjid, setSelectedMenuObjid] = useState<number | null>(null);
const [menuPopoverOpen, setMenuPopoverOpen] = useState(false);
@@ -309,8 +309,8 @@ export function ConditionalContainerConfigPanel({
</>
) : selectedMenuObjid ? (
(() => {
const menu = availableMenus.find((m) => m.menuObjid === selectedMenuObjid);
return menu ? `${menu.parentMenuName} > ${menu.menuName}` : `메뉴 ${selectedMenuObjid}`;
const menu = availableMenus.find((m) => m.menu_objid === selectedMenuObjid);
return menu ? `${menu.parent_menu_name} > ${menu.menu_name}` : `메뉴 ${selectedMenuObjid}`;
})()
) : (
"메뉴 선택..."
@@ -326,10 +326,10 @@ export function ConditionalContainerConfigPanel({
<CommandGroup>
{availableMenus.map((menu) => (
<CommandItem
key={menu.menuObjid}
value={`${menu.parentMenuName} ${menu.menuName}`}
key={menu.menu_objid}
value={`${menu.parent_menu_name} ${menu.menu_name}`}
onSelect={() => {
setSelectedMenuObjid(menu.menuObjid);
setSelectedMenuObjid(menu.menu_objid);
setSelectedCategoryColumn("");
setSelectedCategoryTableName("");
setMenuPopoverOpen(false);
@@ -339,14 +339,14 @@ export function ConditionalContainerConfigPanel({
<Check
className={cn(
"mr-2 h-3 w-3",
selectedMenuObjid === menu.menuObjid ? "opacity-100" : "opacity-0"
selectedMenuObjid === menu.menu_objid ? "opacity-100" : "opacity-0"
)}
/>
<div className="flex flex-col">
<span>{menu.parentMenuName} &gt; {menu.menuName}</span>
{menu.screenCode && (
<span>{menu.parent_menu_name} &gt; {menu.menu_name}</span>
{menu.screen_code && (
<span className="text-[10px] text-muted-foreground">
{menu.screenCode}
{menu.screen_code}
</span>
)}
</div>
@@ -1674,9 +1674,9 @@ function RowNumberingConfigSection({
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 || "",
id: String(rule.rule_id || `rule-${index}`),
name: rule.rule_name || "이름 없음",
code: rule.rule_id || "",
})));
}
} catch (error) {
@@ -1347,8 +1347,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
const response = await apiClient.get(`/table-categories/${leftTableName}/${columnName}/values`);
if (response.data.success && response.data.data && response.data.data.length > 0) {
return response.data.data.map((item: any) => ({
value: item.valueCode,
label: item.valueLabel,
value: item.value_code,
label: item.value_label,
}));
}
} catch {
@@ -198,7 +198,7 @@ interface AdditionalTabConfigPanelProps {
tabIndex: number;
config: SplitPanelLayoutConfig;
updateRightPanel: (updates: Partial<SplitPanelLayoutConfig["rightPanel"]>) => void;
availableRightTables: TableInfo[];
availableRightTables: any[];
leftTableColumns: ColumnInfo[];
menuObjid?: number;
// 공유 컬럼 로드 상태
@@ -323,17 +323,17 @@ const AdditionalTabConfigPanel: React.FC<AdditionalTabConfigPanelProps> = ({
<CommandGroup className="max-h-[200px] overflow-auto">
{availableRightTables.map((table) => (
<CommandItem
key={table.tableName}
value={`${table.displayName || ""} ${table.tableName}`}
onSelect={() => updateTab({ tableName: table.tableName, columns: [] })}
key={table.table_name}
value={`${table.display_name || ""} ${table.table_name}`}
onSelect={() => updateTab({ tableName: table.table_name, columns: [] })}
>
<Check
className={cn(
"mr-2 h-4 w-4",
tab.tableName === table.tableName ? "opacity-100" : "opacity-0"
tab.tableName === table.table_name ? "opacity-100" : "opacity-0"
)}
/>
{table.displayName || table.tableName}
{table.display_name || table.table_name}
</CommandItem>
))}
</CommandGroup>
@@ -1431,8 +1431,8 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
<Database className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
<span className="truncate">
{config.leftPanel?.tableName
? allTables.find((t) => (t.tableName || t.table_name) === config.leftPanel?.tableName)?.tableLabel ||
allTables.find((t) => (t.tableName || t.table_name) === config.leftPanel?.tableName)?.displayName ||
? allTables.find((t) => t.table_name === config.leftPanel?.tableName)?.table_label ||
allTables.find((t) => t.table_name === config.leftPanel?.tableName)?.display_name ||
config.leftPanel?.tableName
: "테이블을 선택하세요"}
</span>
@@ -1466,8 +1466,8 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
)}
/>
<Database className="mr-2 h-3.5 w-3.5 text-primary" />
{allTables.find((t) => (t.tableName || t.table_name) === screenTableName)?.tableLabel ||
allTables.find((t) => (t.tableName || t.table_name) === screenTableName)?.displayName ||
{allTables.find((t) => t.table_name === screenTableName)?.table_label ||
allTables.find((t) => t.table_name === screenTableName)?.display_name ||
screenTableName}
</CommandItem>
</CommandGroup>
@@ -1476,10 +1476,10 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
{/* 전체 테이블 */}
<CommandGroup heading="전체 테이블">
{allTables
.filter((t) => (t.tableName || t.table_name) !== screenTableName)
.filter((t) => t.table_name !== screenTableName)
.map((table) => {
const tableName = table.tableName || table.table_name;
const displayName = table.tableLabel || table.displayName || tableName;
const tableName = table.table_name;
const displayName = table.table_label || table.display_name || tableName;
return (
<CommandItem
key={tableName}
@@ -1823,21 +1823,21 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
<CommandGroup className="max-h-[200px] overflow-auto">
{availableRightTables.map((table) => (
<CommandItem
key={table.tableName}
value={`${table.displayName || ""} ${table.tableName}`}
key={table.table_name}
value={`${table.display_name || ""} ${table.table_name}`}
onSelect={() => {
updateRightPanel({ tableName: table.tableName });
updateRightPanel({ tableName: table.table_name });
setRightTableOpen(false);
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
config.rightPanel?.tableName === table.tableName ? "opacity-100" : "opacity-0",
config.rightPanel?.tableName === table.table_name ? "opacity-100" : "opacity-0",
)}
/>
{table.displayName || table.tableName}
{table.displayName && <span className="ml-2 text-xs text-muted-foreground">({table.tableName})</span>}
{table.display_name || table.table_name}
{table.display_name && <span className="ml-2 text-xs text-muted-foreground">({table.table_name})</span>}
</CommandItem>
))}
</CommandGroup>
@@ -672,11 +672,11 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
const fileData = response.files || (response as any).data || [];
const chunkFiles = fileData.map((file: any) => ({
objid: file.objid || file.id,
savedFileName: file.saved_file_name || file.savedFileName,
realFileName: file.real_file_name || file.realFileName || file.name,
fileSize: file.file_size || file.fileSize || file.size,
fileExt: file.file_ext || file.fileExt || file.extension,
filePath: file.file_path || file.filePath || file.path,
savedFileName: file.saved_file_name,
realFileName: file.real_file_name || file.name,
fileSize: file.file_size || file.size,
fileExt: file.file_ext || file.extension,
filePath: file.file_path || file.path,
docType: file.doc_type,
docTypeName: file.doc_type_name,
targetObjid: file.target_objid,
@@ -2012,8 +2012,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
// 컬럼명을 라벨로 변환하는 함수
const getColumnLabel = useCallback(
(columnName: string) => {
const column = rightTableColumns.find((col) => col.columnName === columnName || col.column_name === columnName);
return column?.columnLabel || column?.column_label || column?.displayName || columnName;
const column = rightTableColumns.find((col) => col.column_name === columnName);
return column?.column_label || column?.display_name || columnName;
},
[rightTableColumns],
);
@@ -2030,8 +2030,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
const response = await apiClient.get(`/table-categories/${leftTableName}/${columnName}/values`);
if (response.data.success && response.data.data && response.data.data.length > 0) {
return response.data.data.map((item: any) => ({
value: item.valueCode,
label: item.valueLabel,
value: item.value_code,
label: item.value_label,
}));
}
} catch {
@@ -2203,8 +2203,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
const columnsResponse = await tableTypeApi.getColumns(leftTableName);
const labels: Record<string, string> = {};
columnsResponse.forEach((col: any) => {
const columnName = col.columnName || col.column_name;
const label = col.columnLabel || col.column_label || col.displayName || columnName;
const columnName = col.column_name;
const label = col.column_label || col.display_name || columnName;
if (columnName) {
labels[columnName] = label;
}
@@ -2231,8 +2231,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
// 우측 컬럼 라벨도 함께 로드
const labels: Record<string, string> = {};
columnsResponse.forEach((col: any) => {
const columnName = col.columnName || col.column_name;
const label = col.columnLabel || col.column_label || col.displayName || columnName;
const columnName = col.column_name;
const label = col.column_label || col.display_name || columnName;
if (columnName) {
labels[columnName] = label;
}
@@ -2251,9 +2251,9 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
try {
const inputTypesResponse = await tableTypeApi.getColumnInputTypes(tbl);
inputTypesResponse.forEach((col: any) => {
const colName = col.columnName || col.column_name;
const colName = col.column_name;
if (colName) {
inputTypes[colName] = col.inputType || "text";
inputTypes[colName] = col.input_type || "text";
}
});
} catch {
@@ -2293,10 +2293,10 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
for (const tableName of tablesToLoad) {
try {
const columnsResponse = await tableTypeApi.getColumns(tableName);
const categoryColumns = columnsResponse.filter((col: any) => col.inputType === "category");
const categoryColumns = columnsResponse.filter((col: any) => col.input_type === "category");
for (const col of categoryColumns) {
const columnName = col.columnName || col.column_name;
const columnName = col.column_name;
try {
const response = await apiClient.get(
`/table-categories/${tableName}/${columnName}/values?includeInactive=true`,
@@ -2305,8 +2305,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
if (response.data.success && response.data.data) {
const valueMap: Record<string, { label: string; color?: string }> = {};
response.data.data.forEach((item: any) => {
valueMap[item.value_code || item.valueCode] = {
label: item.value_label || item.valueLabel,
valueMap[item.value_code] = {
label: item.value_label,
color: item.color,
};
});
@@ -2379,11 +2379,11 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
try {
// 1. 컬럼 메타 정보 조회
const columnsResponse = await tableTypeApi.getColumns(tableName);
const categoryColumns = columnsResponse.filter((col: any) => col.inputType === "category");
const categoryColumns = columnsResponse.filter((col: any) => col.input_type === "category");
// 2. 각 카테고리 컬럼에 대한 값 조회
for (const col of categoryColumns) {
const columnName = col.columnName || col.column_name;
const columnName = col.column_name;
try {
const response = await apiClient.get(
`/table-categories/${tableName}/${columnName}/values?includeInactive=true`,
@@ -2392,8 +2392,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
if (response.data.success && response.data.data) {
const valueMap: Record<string, { label: string; color?: string }> = {};
response.data.data.forEach((item: any) => {
valueMap[item.value_code || item.valueCode] = {
label: item.value_label || item.valueLabel,
valueMap[item.value_code] = {
label: item.value_label,
color: item.color,
};
});
@@ -312,15 +312,15 @@ const GroupByColumnsSelector: React.FC<{
) : (
<div className="max-h-[200px] space-y-1 overflow-y-auto rounded-md border p-3">
{columns.map((col) => (
<div key={col.columnName} className="flex items-center gap-2">
<div key={col.column_name} className="flex items-center gap-2">
<Checkbox
id={`groupby-${col.columnName}`}
checked={selectedColumns.includes(col.columnName)}
onCheckedChange={() => toggleColumn(col.columnName)}
id={`groupby-${col.column_name}`}
checked={selectedColumns.includes(col.column_name)}
onCheckedChange={() => toggleColumn(col.column_name)}
/>
<label htmlFor={`groupby-${col.columnName}`} className="flex-1 cursor-pointer text-xs">
{col.columnLabel || col.columnName}
<span className="text-muted-foreground ml-1">({col.columnName})</span>
<label htmlFor={`groupby-${col.column_name}`} className="flex-1 cursor-pointer text-xs">
{col.column_label || col.column_name}
<span className="text-muted-foreground ml-1">({col.column_name})</span>
</label>
</div>
))}
@@ -343,7 +343,7 @@ const ScreenSelector: React.FC<{
onChange: (screenId?: number) => void;
}> = ({ value, onChange }) => {
const [open, setOpen] = useState(false);
const [screens, setScreens] = useState<Array<{ screenId: number; screenName: string; screenCode: string }>>([]);
const [screens, setScreens] = useState<Array<{ screen_id: number; screen_name: string; screen_code: string }>>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
@@ -353,7 +353,7 @@ const ScreenSelector: React.FC<{
const { screenApi } = await import("@/lib/api/screen");
const response = await screenApi.getScreens({ page: 1, size: 1000 });
setScreens(
response.data.map((s) => ({ screenId: s.screenId, screenName: s.screenName, screenCode: s.screenCode })),
response.data.map((s) => ({ screen_id: s.screen_id, screen_name: s.screen_name, screen_code: s.screen_code })),
);
} catch (error) {
console.error("화면 목록 로드 실패:", error);
@@ -364,7 +364,7 @@ const ScreenSelector: React.FC<{
loadScreens();
}, []);
const selectedScreen = screens.find((s) => s.screenId === value);
const selectedScreen = screens.find((s) => s.screen_id === value);
return (
<Popover open={open} onOpenChange={setOpen}>
@@ -376,7 +376,7 @@ const ScreenSelector: React.FC<{
className="h-8 w-full justify-between text-xs"
disabled={loading}
>
{loading ? "로딩 중..." : selectedScreen ? selectedScreen.screenName : "화면 선택"}
{loading ? "로딩 중..." : selectedScreen ? selectedScreen.screen_name : "화면 선택"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
@@ -388,18 +388,18 @@ const ScreenSelector: React.FC<{
<CommandGroup className="max-h-[300px] overflow-auto">
{screens.map((screen) => (
<CommandItem
key={screen.screenId}
value={`${screen.screenName.toLowerCase()} ${screen.screenCode.toLowerCase()} ${screen.screenId}`}
key={screen.screen_id}
value={`${screen.screen_name.toLowerCase()} ${screen.screen_code.toLowerCase()} ${screen.screen_id}`}
onSelect={() => {
onChange(screen.screenId === value ? undefined : screen.screenId);
onChange(screen.screen_id === value ? undefined : screen.screen_id);
setOpen(false);
}}
className="text-xs"
>
<Check className={cn("mr-2 h-4 w-4", value === screen.screenId ? "opacity-100" : "opacity-0")} />
<Check className={cn("mr-2 h-4 w-4", value === screen.screen_id ? "opacity-100" : "opacity-0")} />
<div className="flex flex-col">
<span className="font-medium">{screen.screenName}</span>
<span className="text-muted-foreground text-[10px]">{screen.screenCode}</span>
<span className="font-medium">{screen.screen_name}</span>
<span className="text-muted-foreground text-[10px]">{screen.screen_code}</span>
</div>
</CommandItem>
))}
@@ -546,16 +546,16 @@ const AdditionalTabConfigPanel: React.FC<AdditionalTabConfigPanelProps> = ({
<CommandGroup className="max-h-[200px] overflow-auto">
{availableRightTables.map((table) => (
<CommandItem
key={table.tableName}
value={`${table.displayName || ""} ${table.tableName}`}
onSelect={() => updateTab({ tableName: table.tableName, columns: [] })}
key={table.table_name}
value={`${table.display_name || ""} ${table.table_name}`}
onSelect={() => updateTab({ tableName: table.table_name, columns: [] })}
>
<Check
className={cn("mr-2 h-4 w-4", tab.tableName === table.tableName ? "opacity-100" : "opacity-0")}
className={cn("mr-2 h-4 w-4", tab.tableName === table.table_name ? "opacity-100" : "opacity-0")}
/>
{table.displayName || table.tableName}
{table.displayName && (
<span className="text-muted-foreground ml-2 text-xs">({table.tableName})</span>
{table.display_name || table.table_name}
{table.display_name && (
<span className="text-muted-foreground ml-2 text-xs">({table.table_name})</span>
)}
</CommandItem>
))}
@@ -1645,23 +1645,23 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
console.log(`📊 테이블 ${tableName} 컬럼 응답:`, columnsResponse);
const columns: ColumnInfo[] = (columnsResponse || []).map((col: any) => ({
tableName: col.tableName || tableName,
columnName: col.columnName || col.column_name,
columnLabel: col.displayName || col.columnLabel || col.column_label || col.columnName || col.column_name,
dataType: col.dataType || col.data_type || col.dbType,
webType: col.webType || col.web_type,
input_type: col.inputType || col.input_type,
widgetType: col.widgetType || col.widget_type || col.webType || col.web_type,
isNullable: col.isNullable || col.is_nullable,
required: col.required !== undefined ? col.required : col.isNullable === "NO" || col.is_nullable === "NO",
columnDefault: col.columnDefault || col.column_default,
characterMaximumLength: col.characterMaximumLength || col.character_maximum_length,
tableName: col.table_name || tableName,
columnName: col.column_name,
columnLabel: col.display_name || col.column_label || col.column_name,
dataType: col.data_type,
webType: col.web_type,
input_type: col.input_type,
widgetType: col.widget_type || col.web_type,
isNullable: col.is_nullable,
required: col.required !== undefined ? col.required : col.is_nullable === "NO",
columnDefault: col.column_default,
characterMaximumLength: col.character_maximum_length,
isPrimaryKey: col.isPrimaryKey || false, // PK 여부 추가
codeCategory: col.codeCategory || col.code_category,
codeValue: col.codeValue || col.code_value,
referenceTable: col.referenceTable || col.reference_table, // 🆕 참조 테이블
referenceColumn: col.referenceColumn || col.reference_column, // 🆕 참조 컬럼
displayColumn: col.displayColumn || col.display_column, // 🆕 표시 컬럼
codeCategory: col.code_category,
codeValue: col.code_value,
referenceTable: col.reference_table, // 🆕 참조 테이블
referenceColumn: col.reference_column, // 🆕 참조 컬럼
displayColumn: col.display_column, // 🆕 표시 컬럼
}));
console.log(`✅ 테이블 ${tableName} 컬럼 ${columns.length}개 로드됨:`, columns);
@@ -1733,11 +1733,11 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
try {
const refColumnsResponse = await tableTypeApi.getColumns(refTableName);
const refColumns: ColumnInfo[] = (refColumnsResponse || []).map((col: any) => ({
tableName: col.tableName || refTableName,
columnName: col.columnName || col.column_name,
columnLabel: col.displayName || col.columnLabel || col.column_label || col.columnName || col.column_name,
dataType: col.dataType || col.data_type || col.dbType,
input_type: col.inputType || col.input_type,
tableName: col.table_name || refTableName,
columnName: col.column_name,
columnLabel: col.display_name || col.column_label || col.column_name,
dataType: col.data_type,
input_type: col.input_type,
}));
referenceTableData.push({ tableName: refTableName, columns: refColumns });
@@ -2061,10 +2061,10 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
<Database className="text-muted-foreground h-3.5 w-3.5 shrink-0" />
<span className="truncate">
{config.leftPanel?.tableName
? allTables.find((t) => (t.tableName || t.table_name) === config.leftPanel?.tableName)
?.tableLabel ||
allTables.find((t) => (t.tableName || t.table_name) === config.leftPanel?.tableName)
?.displayName ||
? allTables.find((t) => t.table_name === config.leftPanel?.tableName)
?.table_label ||
allTables.find((t) => t.table_name === config.leftPanel?.tableName)
?.display_name ||
config.leftPanel?.tableName
: "테이블을 선택하세요"}
</span>
@@ -2098,8 +2098,8 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
)}
/>
<Database className="text-primary mr-2 h-3.5 w-3.5" />
{allTables.find((t) => (t.tableName || t.table_name) === screenTableName)?.tableLabel ||
allTables.find((t) => (t.tableName || t.table_name) === screenTableName)?.displayName ||
{allTables.find((t) => t.table_name === screenTableName)?.table_label ||
allTables.find((t) => t.table_name === screenTableName)?.display_name ||
screenTableName}
</CommandItem>
</CommandGroup>
@@ -2108,10 +2108,10 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
{/* 전체 테이블 */}
<CommandGroup heading="전체 테이블">
{allTables
.filter((t) => (t.tableName || t.table_name) !== screenTableName)
.filter((t) => t.table_name !== screenTableName)
.map((table) => {
const tableName = table.tableName || table.table_name;
const displayName = table.tableLabel || table.displayName || tableName;
const tableName = table.table_name;
const displayName = table.table_label || table.display_name || tableName;
return (
<CommandItem
key={tableName}
@@ -2784,22 +2784,22 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
<CommandGroup className="max-h-[200px] overflow-auto">
{availableRightTables.map((table) => (
<CommandItem
key={table.tableName}
value={`${table.displayName || ""} ${table.tableName}`}
key={table.table_name}
value={`${table.display_name || ""} ${table.table_name}`}
onSelect={() => {
updateRightPanel({ tableName: table.tableName });
updateRightPanel({ tableName: table.table_name });
setRightTableOpen(false);
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
config.rightPanel?.tableName === table.tableName ? "opacity-100" : "opacity-0",
config.rightPanel?.tableName === table.table_name ? "opacity-100" : "opacity-0",
)}
/>
{table.displayName || table.tableName}
{table.displayName && (
<span className="text-muted-foreground ml-2 text-xs">({table.tableName})</span>
{table.display_name || table.table_name}
{table.display_name && (
<span className="text-muted-foreground ml-2 text-xs">({table.table_name})</span>
)}
</CommandItem>
))}
@@ -75,8 +75,8 @@ export const LeftPanelConfigTab: React.FC<LeftPanelConfigTabProps> = ({
<Database className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
<span className="truncate">
{config.leftPanel?.tableName
? allTables.find((t) => (t.tableName || t.table_name) === config.leftPanel?.tableName)?.tableLabel ||
allTables.find((t) => (t.tableName || t.table_name) === config.leftPanel?.tableName)?.displayName ||
? allTables.find((t) => t.table_name === config.leftPanel?.tableName)?.table_label ||
allTables.find((t) => t.table_name === config.leftPanel?.tableName)?.display_name ||
config.leftPanel?.tableName
: "테이블을 선택하세요"}
</span>
@@ -108,8 +108,8 @@ export const LeftPanelConfigTab: React.FC<LeftPanelConfigTabProps> = ({
)}
/>
<Database className="mr-2 h-3.5 w-3.5 text-primary" />
{allTables.find((t) => (t.tableName || t.table_name) === screenTableName)?.tableLabel ||
allTables.find((t) => (t.tableName || t.table_name) === screenTableName)?.displayName ||
{allTables.find((t) => t.table_name === screenTableName)?.table_label ||
allTables.find((t) => t.table_name === screenTableName)?.display_name ||
screenTableName}
</CommandItem>
</CommandGroup>
@@ -99,11 +99,11 @@ export const RightPanelConfigTab: React.FC<RightPanelConfigTabProps> = ({
<CommandEmpty> .</CommandEmpty>
<CommandGroup className="max-h-[200px] overflow-auto">
{availableRightTables.map((table) => {
const tableName = (table as any).tableName || (table as any).table_name;
const tableName = (table as any).table_name;
return (
<CommandItem
key={tableName}
value={`${(table as any).displayName || ""} ${tableName}`}
value={`${(table as any).display_name || ""} ${tableName}`}
onSelect={() => {
updateRightPanel({ tableName });
setRightTableOpen(false);
@@ -115,8 +115,8 @@ export const RightPanelConfigTab: React.FC<RightPanelConfigTabProps> = ({
config.rightPanel?.tableName === tableName ? "opacity-100" : "opacity-0",
)}
/>
{(table as any).displayName || tableName}
{(table as any).displayName && <span className="ml-2 text-xs text-muted-foreground">({tableName})</span>}
{(table as any).display_name || tableName}
{(table as any).display_name && <span className="ml-2 text-xs text-muted-foreground">({tableName})</span>}
</CommandItem>
);
})}
@@ -201,7 +201,7 @@ export const ScreenSelector: React.FC<{
onChange: (screenId?: number) => void;
}> = ({ value, onChange }) => {
const [open, setOpen] = useState(false);
const [screens, setScreens] = useState<Array<{ screenId: number; screenName: string; screenCode: string }>>([]);
const [screens, setScreens] = useState<Array<{ screen_id: number; screen_name: string; screen_code: string }>>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
@@ -211,7 +211,7 @@ export const ScreenSelector: React.FC<{
const { screenApi } = await import("@/lib/api/screen");
const response = await screenApi.getScreens({ page: 1, size: 1000 });
setScreens(
response.data.map((s) => ({ screenId: s.screen_id, screenName: s.screen_name, screenCode: s.screen_code })),
response.data.map((s) => ({ screen_id: s.screen_id, screen_name: s.screen_name, screen_code: s.screen_code })),
);
} catch (error) {
console.error("화면 목록 로드 실패:", error);
@@ -222,7 +222,7 @@ export const ScreenSelector: React.FC<{
loadScreens();
}, []);
const selectedScreen = screens.find((s) => s.screenId === value);
const selectedScreen = screens.find((s) => s.screen_id === value);
return (
<Popover open={open} onOpenChange={setOpen}>
@@ -234,7 +234,7 @@ export const ScreenSelector: React.FC<{
className="h-8 w-full justify-between text-xs"
disabled={loading}
>
{loading ? "로딩 중..." : selectedScreen ? selectedScreen.screenName : "화면 선택"}
{loading ? "로딩 중..." : selectedScreen ? selectedScreen.screen_name : "화면 선택"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
@@ -246,18 +246,18 @@ export const ScreenSelector: React.FC<{
<CommandGroup className="max-h-[300px] overflow-auto">
{screens.map((screen) => (
<CommandItem
key={screen.screenId}
value={`${screen.screenName.toLowerCase()} ${screen.screenCode.toLowerCase()} ${screen.screenId}`}
key={screen.screen_id}
value={`${screen.screen_name.toLowerCase()} ${screen.screen_code.toLowerCase()} ${screen.screen_id}`}
onSelect={() => {
onChange(screen.screenId === value ? undefined : screen.screenId);
onChange(screen.screen_id === value ? undefined : screen.screen_id);
setOpen(false);
}}
className="text-xs"
>
<Check className={cn("mr-2 h-4 w-4", value === screen.screenId ? "opacity-100" : "opacity-0")} />
<Check className={cn("mr-2 h-4 w-4", value === screen.screen_id ? "opacity-100" : "opacity-0")} />
<div className="flex flex-col">
<span className="font-medium">{screen.screenName}</span>
<span className="text-muted-foreground text-[10px]">{screen.screenCode}</span>
<span className="font-medium">{screen.screen_name}</span>
<span className="text-muted-foreground text-[10px]">{screen.screen_code}</span>
</div>
</CommandItem>
))}
@@ -1515,7 +1515,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
try {
// 🔥 FIX: 캐시 키에 회사 코드 포함 (멀티테넌시 지원)
const currentUser = JSON.parse(localStorage.getItem("currentUser") || "{}");
const companyCode = currentUser.companyCode || "UNKNOWN";
const companyCode = currentUser.company_code || "UNKNOWN";
const cacheKey = `columns_${tableConfig.selectedTable}_${companyCode}`;
const cached = tableColumnCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < TABLE_CACHE_TTL) {
@@ -853,13 +853,13 @@ export function PopButtonComponent({ config, label, isDesignMode, screenId, comp
action: "inbound-confirm",
data: {
items: selectedItems,
fieldValues,
field_values: fieldValues,
},
mappings: {
cardList: cardListMapping,
card_list: cardListMapping,
field: fieldMapping,
},
statusChanges: statusChangeRules,
status_changes: statusChangeRules,
});
if (result.data?.success) {
@@ -518,7 +518,7 @@ export function PopCardListV2Component({
const result = await apiClient.post("/pop/execute-action", {
tasks,
data: { items: [{ ...row, id: targetId }], fieldValues: {} },
data: { items: [{ ...row, id: targetId }], field_values: {} },
mappings: {},
});
if (result.data?.success) successCount++;
@@ -1284,7 +1284,7 @@ function CardV2({
try {
const result = await apiClient.post("/pop/execute-action", {
tasks,
data: { items: [targetRow], fieldValues: {} },
data: { items: [targetRow], field_values: {} },
mappings: {},
});
if (result.data?.success) {
@@ -1546,7 +1546,7 @@ function CardV2({
: processId ? { ...actionRow, id: processId } : actionRow;
const result = await apiClient.post("/pop/execute-action", {
tasks,
data: { items: [targetRow], fieldValues: {} },
data: { items: [targetRow], field_values: {} },
mappings: {},
});
if (result.data?.success) {
@@ -331,7 +331,7 @@ function TabData({
const ds = cfg.dataSource;
const selectedDisplay = ds.tableName
? tables.find((t) => t.tableName === ds.tableName)?.displayName || ds.tableName
? tables.find((t) => t.table_name === ds.tableName)?.display_name || ds.tableName
: "";
const toggleColumn = (colName: string) => {
@@ -383,16 +383,16 @@ function TabData({
</CommandItem>
{tables.map((t) => (
<CommandItem
key={t.tableName}
value={`${t.tableName} ${t.displayName || ""}`}
onSelect={() => { onTableChange(t.tableName); setTableOpen(false); }}
key={t.table_name}
value={`${t.table_name} ${t.display_name || ""}`}
onSelect={() => { onTableChange(t.table_name); setTableOpen(false); }}
className="text-xs"
>
<Check className={cn("mr-2 h-3 w-3", ds.tableName === t.tableName ? "opacity-100" : "opacity-0")} />
<Check className={cn("mr-2 h-3 w-3", ds.tableName === t.table_name ? "opacity-100" : "opacity-0")} />
<div className="flex flex-col">
<span>{t.displayName || t.tableName}</span>
{t.displayName && t.displayName !== t.tableName && (
<span className="text-[10px] text-muted-foreground">{t.tableName}</span>
<span>{t.display_name || t.table_name}</span>
{t.display_name && t.display_name !== t.table_name && (
<span className="text-[10px] text-muted-foreground">{t.table_name}</span>
)}
</div>
</CommandItem>
@@ -596,7 +596,7 @@ function JoinItemV2({
targetColumns.some((tc) => tc.name === mc.name && tc.type === mc.type)
);
const selectableTables = tables.filter((t) => t.tableName !== mainTableName);
const selectableTables = tables.filter((t) => t.table_name !== mainTableName);
const hasJoinCondition = join.sourceColumn !== "" && join.targetColumn !== "";
const selectedTargetCols = join.selectedTargetColumns || [];
const pickableTargetCols = targetColumns.filter((tc) => tc.name !== join.targetColumn);
@@ -633,16 +633,16 @@ function JoinItemV2({
<CommandGroup>
{selectableTables.map((t) => (
<CommandItem
key={t.tableName}
value={`${t.tableName} ${t.displayName || ""}`}
key={t.table_name}
value={`${t.table_name} ${t.display_name || ""}`}
onSelect={() => {
onUpdate({ targetTable: t.tableName, sourceColumn: "", targetColumn: "", selectedTargetColumns: [] });
onUpdate({ targetTable: t.table_name, sourceColumn: "", targetColumn: "", selectedTargetColumns: [] });
setTableOpen(false);
}}
className="text-[10px]"
>
<Check className={cn("mr-1 h-3 w-3", join.targetTable === t.tableName ? "opacity-100" : "opacity-0")} />
{t.tableName}
<Check className={cn("mr-1 h-3 w-3", join.targetTable === t.table_name ? "opacity-100" : "opacity-0")} />
{t.table_name}
</CommandItem>
))}
</CommandGroup>
@@ -1635,16 +1635,16 @@ function TimelineConfigEditor({
<CommandGroup>
{tables.map((t) => (
<CommandItem
key={t.tableName}
value={`${t.tableName} ${t.displayName || ""}`}
key={t.table_name}
value={`${t.table_name} ${t.display_name || ""}`}
onSelect={() => {
updateSource({ processTable: t.tableName, foreignKey: "", seqColumn: "", nameColumn: "", statusColumn: "" });
updateSource({ processTable: t.table_name, foreignKey: "", seqColumn: "", nameColumn: "", statusColumn: "" });
setTableOpen(false);
}}
className="text-[10px]"
>
<Check className={cn("mr-1 h-3 w-3", src.processTable === t.tableName ? "opacity-100" : "opacity-0")} />
{t.displayName || t.tableName}
<Check className={cn("mr-1 h-3 w-3", src.processTable === t.table_name ? "opacity-100" : "opacity-0")} />
{t.display_name || t.table_name}
</CommandItem>
))}
</CommandGroup>
@@ -2726,15 +2726,15 @@ function DbTableCombobox({
const q = search.toLowerCase();
return tables.filter(
(t) =>
t.tableName.toLowerCase().includes(q) ||
(t.tableComment || "").toLowerCase().includes(q),
t.table_name.toLowerCase().includes(q) ||
(t.description || "").toLowerCase().includes(q),
);
}, [tables, search]);
const selectedLabel = useMemo(() => {
if (!value) return "DB 테이블 검색...";
const found = tables.find((t) => t.tableName === value);
return found ? `${found.tableName}${found.tableComment ? ` (${found.tableComment})` : ""}` : value;
const found = tables.find((t) => t.table_name === value);
return found ? `${found.table_name}${found.description ? ` (${found.description})` : ""}` : value;
}, [value, tables]);
return (
@@ -2769,19 +2769,19 @@ function DbTableCombobox({
<CommandGroup>
{filtered.map((t) => (
<CommandItem
key={t.tableName}
value={t.tableName}
key={t.table_name}
value={t.table_name}
onSelect={() => {
onSelect(t.tableName);
onSelect(t.table_name);
setOpen(false);
setSearch("");
}}
className="text-[10px]"
>
<Check className={cn("mr-1.5 h-3 w-3", value === t.tableName ? "opacity-100" : "opacity-0")} />
<span className="font-medium">{t.tableName}</span>
{t.tableComment && (
<span className="ml-1 text-muted-foreground">({t.tableComment})</span>
<Check className={cn("mr-1.5 h-3 w-3", value === t.table_name ? "opacity-100" : "opacity-0")} />
<span className="font-medium">{t.table_name}</span>
{t.description && (
<span className="ml-1 text-muted-foreground">({t.description})</span>
)}
</CommandItem>
))}
+2 -2
View File
@@ -385,8 +385,8 @@ export class EnhancedFormService {
): Promise<{ success: boolean; message?: string; data?: any }> {
try {
const result = await dynamicFormApi.saveData({
screenId,
tableName,
screen_id: screenId,
table_name: tableName,
data: formData,
});
+33 -38
View File
@@ -667,13 +667,8 @@ export class ButtonActionExecutor {
firstItem.location_name &&
firstItem.row_num !== undefined &&
firstItem.level_num !== undefined;
const isOldFormat =
firstItem.locationCode &&
firstItem.locationName &&
firstItem.rowNum !== undefined &&
firstItem.levelNum !== undefined;
if (isNewFormat || isOldFormat) {
if (isNewFormat) {
rackStructureLocations = value;
rackStructureFieldKey = key;
break;
@@ -937,8 +932,8 @@ export class ButtonActionExecutor {
delete dataWithMeta.id;
const insertResult = await DynamicFormApi.saveFormData({
screenId: context.screenId || 0,
tableName: repeaterTargetTable,
screen_id: context.screenId || 0,
table_name: repeaterTargetTable,
data: dataWithMeta as Record<string, any>,
});
} else if (item.id && _existingRecord === true) {
@@ -1075,7 +1070,7 @@ export class ButtonActionExecutor {
// 전체 업데이트 (originalData 없이 id로 UPDATE 판단된 경우)
saveResult = await DynamicFormApi.updateFormData(primaryKeyValue, {
tableName,
table_name: tableName,
data: formData,
});
}
@@ -1533,8 +1528,8 @@ export class ButtonActionExecutor {
saveResult = { success: true, message: "RepeaterFieldGroup/RepeatScreenModal/V2Repeater에서 처리" };
} else {
saveResult = await DynamicFormApi.saveFormData({
screenId,
tableName,
screen_id: screenId,
table_name: tableName,
data: dataWithUserInfo,
});
}
@@ -2095,7 +2090,7 @@ export class ButtonActionExecutor {
// 저장 전 중복 체크
const firstLocation = locations[0];
const warehouseCode = firstLocation.warehouse_code || firstLocation.warehouse_id || firstLocation.warehouseCode;
const warehouseCode = firstLocation.warehouse_code || firstLocation.warehouse_id;
const floor = firstLocation.floor;
const zone = firstLocation.zone;
@@ -2124,14 +2119,14 @@ export class ButtonActionExecutor {
const existingSet = new Set(existingData.map((loc: any) => `${loc.row_num}-${loc.level_num}`));
const duplicates = locations.filter((loc) => {
const key = `${loc.row_num || loc.rowNum}-${loc.level_num || loc.levelNum}`;
const key = `${loc.row_num}-${loc.level_num}`;
return existingSet.has(key);
});
if (duplicates.length > 0) {
const duplicateInfo = duplicates
.slice(0, 5)
.map((d) => `${d.row_num || d.rowNum}${d.level_num || d.levelNum}`)
.map((d) => `${d.row_num}${d.level_num}`)
.join(", ");
const moreCount = duplicates.length > 5 ? `${duplicates.length - 5}` : "";
@@ -2153,17 +2148,17 @@ export class ButtonActionExecutor {
// 새로운 형식(스네이크 케이스)과 기존 형식(카멜 케이스) 모두 지원
const record: Record<string, any> = {
// 렉 구조에서 생성된 필드 (이미 테이블 컬럼명과 동일)
location_code: loc.location_code || loc.locationCode,
location_name: loc.location_name || loc.locationName,
row_num: loc.row_num || String(loc.rowNum),
level_num: loc.level_num || String(loc.levelNum),
location_code: loc.location_code,
location_name: loc.location_name,
row_num: loc.row_num,
level_num: loc.level_num,
// 창고 정보 (렉 구조 컴포넌트에서 전달) - DB 컬럼명은 warehouse_code
warehouse_code: loc.warehouse_code || loc.warehouse_id || loc.warehouseCode,
warehouse_name: loc.warehouse_name || loc.warehouseName,
warehouse_code: loc.warehouse_code || loc.warehouse_id,
warehouse_name: loc.warehouse_name,
// 위치 정보 (렉 구조 컴포넌트에서 전달)
floor: loc.floor,
zone: loc.zone,
location_type: loc.location_type || loc.locationType,
location_type: loc.location_type,
status: loc.status || "사용",
// 사용자 정보 추가
writer: userId,
@@ -2183,8 +2178,8 @@ export class ButtonActionExecutor {
const record = recordsToInsert[i];
try {
const result = await DynamicFormApi.saveFormData({
screenId,
tableName,
screen_id: screenId,
table_name: tableName,
data: record,
});
@@ -2474,15 +2469,15 @@ export class ButtonActionExecutor {
if (isMainUpdate) {
mainSaveResult = await DynamicFormApi.updateFormData(existingMainId, {
tableName: tableName!,
table_name: tableName!,
data: mainRowToSave,
});
mainRecordId = existingMainId;
} else {
console.log(" [handleUniversalFormModalTableSectionSave] 메인 테이블 INSERT 실행");
mainSaveResult = await DynamicFormApi.saveFormData({
screenId: screenId!,
tableName: tableName!,
screen_id: screenId!,
table_name: tableName!,
data: mainRowToSave,
});
mainRecordId = mainSaveResult.data?.id || null;
@@ -2546,8 +2541,8 @@ export class ButtonActionExecutor {
console.log(" [INSERT] 신규 품목:", { tableName: saveTableName, data: rowToSave });
const saveResult = await DynamicFormApi.saveFormData({
screenId: screenId!,
tableName: saveTableName,
screen_id: screenId!,
table_name: saveTableName,
data: rowToSave,
});
@@ -2583,7 +2578,7 @@ export class ButtonActionExecutor {
});
const updateResult = await DynamicFormApi.updateFormData(item.id, {
tableName: saveTableName,
table_name: saveTableName,
data: rowToUpdate,
});
@@ -2937,8 +2932,8 @@ export class ButtonActionExecutor {
// INSERT 실행
const { DynamicFormApi } = await import("@/lib/api/dynamicForm");
const saveResult = await DynamicFormApi.saveFormData({
screenId,
tableName,
screen_id: screenId,
table_name: tableName,
data: dataWithUserInfo,
});
@@ -4725,8 +4720,8 @@ export class ButtonActionExecutor {
}
const result = await DynamicFormApi.saveFormData({
screenId: 0, // 임시값
tableName: context.tableName,
screen_id: 0, // 임시값
table_name: context.tableName,
data: saveData,
});
@@ -4817,7 +4812,7 @@ export class ButtonActionExecutor {
}
const result = await DynamicFormApi.updateFormData(updateId, {
tableName: context.tableName,
table_name: context.tableName,
data: updateData,
});
@@ -4955,8 +4950,8 @@ export class ButtonActionExecutor {
const targetTable = action.fieldMappings?.[0]?.targetTable || action.targetTable || "test_project_info";
const formDataPayload = {
screenId: 0, // 제어 관리에서는 screenId가 없으므로 0 사용
tableName: targetTable, // 필드 매핑에서 대상 테이블명 가져오기
screen_id: 0, // 제어 관리에서는 screen_id가 없으므로 0 사용
table_name: targetTable, // 필드 매핑에서 대상 테이블명 가져오기
data: insertData,
};
@@ -5445,8 +5440,8 @@ export class ButtonActionExecutor {
// valueCode → valueLabel 매핑
categoryMap[columnName] = {};
valuesResponse.data.forEach((catValue: any) => {
const code = catValue.valueCode || catValue.category_value_id;
const label = catValue.valueLabel || catValue.label || code;
const code = catValue.value_code || catValue.category_value_id;
const label = catValue.value_label || catValue.label || code;
if (code) {
categoryMap[columnName][code] = label;
}