feat(UniversalFormModal): 전용 API 저장 기능 및 사원+부서 통합 저장 API 구현

- CustomApiSaveConfig 타입 정의 (apiType, mainDeptFields, subDeptFields)

- saveWithCustomApi() 함수 추가로 테이블 직접 저장 대신 전용 API 호출

- adminController에 saveUserWithDept(), getUserWithDept() API 추가

- user_info + user_dept 트랜잭션 저장, 메인 부서 변경 시 자동 겸직 전환

- ConfigPanel에 전용 API 저장 설정 UI 추가

- SplitPanelLayout2: getColumnValue()로 조인 테이블 컬럼 값 추출 개선

- 검색 컬럼 선택 시 표시 컬럼 기반으로 변경
This commit is contained in:
SeongHyun Kim
2025-12-08 11:33:35 +09:00
parent a5055cae15
commit 892278853c
8 changed files with 1311 additions and 188 deletions

View File

@@ -200,7 +200,11 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
// 선택된 컬럼만 병합
const mergedItem = { ...item };
joinConfig.selectColumns.forEach((col) => {
// alias가 있으면 alias_컬럼명, 없으면 그냥 컬럼명
// 조인 테이블명.컬럼명 형식으로 저장 (sourceTable 참조용)
const tableColumnKey = `${joinConfig.joinTable}.${col}`;
mergedItem[tableColumnKey] = joinRow[col];
// alias가 있으면 alias_컬럼명, 없으면 그냥 컬럼명으로도 저장 (하위 호환성)
const targetKey = joinConfig.alias ? `${joinConfig.alias}_${col}` : col;
// 메인 테이블에 같은 컬럼이 없으면 추가
if (!(col in mergedItem)) {
@@ -210,6 +214,7 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
mergedItem[targetKey] = joinRow[col];
}
});
console.log(`[SplitPanelLayout2] 조인 데이터 병합:`, { mainKey: joinKey, mergedKeys: Object.keys(mergedItem) });
return mergedItem;
}
@@ -738,6 +743,37 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
};
}, [screenContext, component.id]);
// 컬럼 값 가져오기 (sourceTable 고려)
const getColumnValue = useCallback((item: any, col: ColumnConfig): any => {
// col.name이 "테이블명.컬럼명" 형식인 경우 처리
const actualColName = col.name.includes(".") ? col.name.split(".")[1] : col.name;
const tableFromName = col.name.includes(".") ? col.name.split(".")[0] : null;
const effectiveSourceTable = col.sourceTable || tableFromName;
// sourceTable이 설정되어 있고, 메인 테이블이 아닌 경우
if (effectiveSourceTable && effectiveSourceTable !== config.rightPanel?.tableName) {
// 1. 테이블명.컬럼명 형식으로 먼저 시도 (mergeJoinData에서 저장한 형식)
const tableColumnKey = `${effectiveSourceTable}.${actualColName}`;
if (item[tableColumnKey] !== undefined) {
return item[tableColumnKey];
}
// 2. 조인 테이블의 alias가 설정된 경우 alias_컬럼명으로 시도
const joinTable = config.rightPanel?.joinTables?.find(jt => jt.joinTable === effectiveSourceTable);
if (joinTable?.alias) {
const aliasKey = `${joinTable.alias}_${actualColName}`;
if (item[aliasKey] !== undefined) {
return item[aliasKey];
}
}
// 3. 그냥 컬럼명으로 시도 (메인 테이블에 없는 경우 조인 데이터가 직접 들어감)
if (item[actualColName] !== undefined) {
return item[actualColName];
}
}
// 4. 기본: 컬럼명으로 직접 접근
return item[actualColName];
}, [config.rightPanel?.tableName, config.rightPanel?.joinTables]);
// 값 포맷팅
const formatValue = (value: any, format?: ColumnConfig["format"]): string => {
if (value === null || value === undefined) return "-";
@@ -916,7 +952,7 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
{nameRowColumns.length > 0 && (
<div className="flex flex-wrap items-center gap-x-4 gap-y-1">
{nameRowColumns.map((col, idx) => {
const value = item[col.name];
const value = getColumnValue(item, col);
if (value === null || value === undefined) return null;
return (
<span key={idx} className="flex items-center gap-1">
@@ -931,7 +967,7 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
{infoRowColumns.length > 0 && (
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-muted-foreground">
{infoRowColumns.map((col, idx) => {
const value = item[col.name];
const value = getColumnValue(item, col);
if (value === null || value === undefined) return null;
return (
<span key={idx} className="flex items-center gap-1">
@@ -950,7 +986,7 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
{nameRowColumns.length > 0 && (
<div className="flex flex-wrap items-center gap-2">
{nameRowColumns.map((col, idx) => {
const value = item[col.name];
const value = getColumnValue(item, col);
if (value === null || value === undefined) return null;
if (idx === 0) {
return (
@@ -971,7 +1007,7 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
{infoRowColumns.length > 0 && (
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-muted-foreground">
{infoRowColumns.map((col, idx) => {
const value = item[col.name];
const value = getColumnValue(item, col);
if (value === null || value === undefined) return null;
return (
<span key={idx} className="text-sm">
@@ -1079,7 +1115,7 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
)}
{displayColumns.map((col, colIdx) => (
<TableCell key={colIdx}>
{formatValue(item[col.name], col.format)}
{formatValue(getColumnValue(item, col), col.format)}
</TableCell>
))}
{(config.rightPanel?.showEditButton || config.rightPanel?.showDeleteButton) && (