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:
@@ -279,12 +279,14 @@ export const SplitPanelLayout2ConfigPanel: React.FC<SplitPanelLayout2ConfigPanel
|
||||
column_comment: c.displayName ?? c.column_comment ?? c.label ?? "",
|
||||
}));
|
||||
|
||||
// 선택된 컬럼 추가 (테이블명으로 구분)
|
||||
// 선택된 컬럼 추가 (테이블명으로 구분, 유니크 키 생성)
|
||||
jt.selectColumns.forEach((selCol) => {
|
||||
const col = transformedColumns.find((c: ColumnInfo) => c.column_name === selCol);
|
||||
if (col) {
|
||||
joinColumns.push({
|
||||
...col,
|
||||
// 유니크 키를 위해 테이블명_컬럼명 형태로 저장
|
||||
column_name: `${jt.joinTable}.${col.column_name}`,
|
||||
column_comment: col.column_comment ? `${col.column_comment} (${jt.joinTable})` : `${col.column_name} (${jt.joinTable})`,
|
||||
});
|
||||
}
|
||||
@@ -727,8 +729,13 @@ export const SplitPanelLayout2ConfigPanel: React.FC<SplitPanelLayout2ConfigPanel
|
||||
const currentColumns = side === "left"
|
||||
? config.leftPanel?.displayColumns || []
|
||||
: config.rightPanel?.displayColumns || [];
|
||||
|
||||
// 기본 테이블 설정 (메인 테이블)
|
||||
const defaultTable = side === "left"
|
||||
? config.leftPanel?.tableName
|
||||
: config.rightPanel?.tableName;
|
||||
|
||||
updateConfig(path, [...currentColumns, { name: "", label: "" }]);
|
||||
updateConfig(path, [...currentColumns, { name: "", label: "", sourceTable: defaultTable || "" }]);
|
||||
};
|
||||
|
||||
// 표시 컬럼 삭제
|
||||
@@ -1083,15 +1090,15 @@ export const SplitPanelLayout2ConfigPanel: React.FC<SplitPanelLayout2ConfigPanel
|
||||
// 선택된 테이블의 컬럼만 필터링
|
||||
const selectedSourceTable = col.sourceTable || config.rightPanel?.tableName;
|
||||
const filteredColumns = rightColumns.filter((c) => {
|
||||
// 조인 테이블 컬럼인지 확인 (column_comment에 테이블명 포함)
|
||||
const isJoinColumn = c.column_comment?.includes("(") && c.column_comment?.includes(")");
|
||||
// 조인 테이블 컬럼인지 확인 (column_name이 "테이블명.컬럼명" 형태)
|
||||
const isJoinColumn = c.column_name.includes(".");
|
||||
|
||||
if (selectedSourceTable === config.rightPanel?.tableName) {
|
||||
// 메인 테이블 선택 시: 조인 컬럼 아닌 것만
|
||||
return !isJoinColumn;
|
||||
} else {
|
||||
// 조인 테이블 선택 시: 해당 테이블 컬럼만
|
||||
return c.column_comment?.includes(`(${selectedSourceTable})`);
|
||||
// 조인 테이블 선택 시: 해당 테이블 컬럼만 (테이블명.컬럼명 형태)
|
||||
return c.column_name.startsWith(`${selectedSourceTable}.`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1163,11 +1170,15 @@ export const SplitPanelLayout2ConfigPanel: React.FC<SplitPanelLayout2ConfigPanel
|
||||
filteredColumns.map((c) => {
|
||||
// 조인 컬럼의 경우 테이블명 제거하고 표시
|
||||
const displayLabel = c.column_comment?.replace(/\s*\([^)]+\)$/, "") || c.column_name;
|
||||
// 실제 컬럼명 (테이블명.컬럼명에서 컬럼명만 추출)
|
||||
const actualColumnName = c.column_name.includes(".")
|
||||
? c.column_name.split(".")[1]
|
||||
: c.column_name;
|
||||
return (
|
||||
<SelectItem key={c.column_name} value={c.column_name}>
|
||||
<span className="flex flex-col">
|
||||
<span>{displayLabel}</span>
|
||||
<span className="text-[10px] text-muted-foreground">{c.column_name}</span>
|
||||
<span className="text-[10px] text-muted-foreground">{actualColumnName}</span>
|
||||
</span>
|
||||
</SelectItem>
|
||||
);
|
||||
@@ -1231,6 +1242,7 @@ export const SplitPanelLayout2ConfigPanel: React.FC<SplitPanelLayout2ConfigPanel
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-6 text-xs"
|
||||
disabled={(config.rightPanel?.displayColumns || []).length === 0}
|
||||
onClick={() => {
|
||||
const current = config.rightPanel?.searchColumns || [];
|
||||
updateConfig("rightPanel.searchColumns", [...current, { columnName: "", label: "" }]);
|
||||
@@ -1240,36 +1252,99 @@ export const SplitPanelLayout2ConfigPanel: React.FC<SplitPanelLayout2ConfigPanel
|
||||
추가
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-[10px] text-muted-foreground mb-2">
|
||||
표시할 컬럼 중 검색에 사용할 컬럼을 선택하세요.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
{(config.rightPanel?.searchColumns || []).map((searchCol, index) => (
|
||||
<div key={index} className="flex items-center gap-2">
|
||||
<ColumnSelect
|
||||
columns={rightColumns}
|
||||
value={searchCol.columnName}
|
||||
onValueChange={(value) => {
|
||||
const current = [...(config.rightPanel?.searchColumns || [])];
|
||||
current[index] = { ...current[index], columnName: value };
|
||||
updateConfig("rightPanel.searchColumns", current);
|
||||
}}
|
||||
placeholder="컬럼 선택"
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-8 w-8 shrink-0 p-0"
|
||||
onClick={() => {
|
||||
const current = config.rightPanel?.searchColumns || [];
|
||||
updateConfig(
|
||||
"rightPanel.searchColumns",
|
||||
current.filter((_, i) => i !== index)
|
||||
);
|
||||
}}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</Button>
|
||||
{(config.rightPanel?.searchColumns || []).map((searchCol, index) => {
|
||||
// 표시할 컬럼 정보를 가져와서 테이블명과 함께 표시
|
||||
const displayColumns = config.rightPanel?.displayColumns || [];
|
||||
|
||||
// 유효한 컬럼만 필터링 (name이 있는 것만)
|
||||
const validDisplayColumns = displayColumns.filter((dc) => dc.name && dc.name.trim() !== "");
|
||||
|
||||
// 현재 선택된 컬럼의 표시 정보
|
||||
const selectedDisplayCol = validDisplayColumns.find((dc) => dc.name === searchCol.columnName);
|
||||
const selectedColInfo = rightColumns.find((c) => c.column_name === searchCol.columnName);
|
||||
const selectedLabel = selectedDisplayCol?.label ||
|
||||
selectedColInfo?.column_comment?.replace(/\s*\([^)]+\)$/, "") ||
|
||||
searchCol.columnName;
|
||||
const selectedTableName = selectedDisplayCol?.sourceTable || config.rightPanel?.tableName || "";
|
||||
const selectedTableLabel = tables.find((t) => t.table_name === selectedTableName)?.table_comment || selectedTableName;
|
||||
|
||||
return (
|
||||
<div key={index} className="flex items-center gap-2">
|
||||
<Select
|
||||
value={searchCol.columnName || ""}
|
||||
onValueChange={(value) => {
|
||||
const current = [...(config.rightPanel?.searchColumns || [])];
|
||||
current[index] = { ...current[index], columnName: value };
|
||||
updateConfig("rightPanel.searchColumns", current);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-9 text-xs flex-1">
|
||||
<SelectValue placeholder="컬럼 선택">
|
||||
{searchCol.columnName ? (
|
||||
<span className="flex items-center gap-1">
|
||||
<span>{selectedLabel}</span>
|
||||
<span className="text-[10px] text-muted-foreground">({selectedTableLabel})</span>
|
||||
</span>
|
||||
) : (
|
||||
"컬럼 선택"
|
||||
)}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{validDisplayColumns.length === 0 ? (
|
||||
<SelectItem value="_empty" disabled>
|
||||
먼저 표시할 컬럼을 추가하세요
|
||||
</SelectItem>
|
||||
) : (
|
||||
validDisplayColumns.map((dc, dcIndex) => {
|
||||
const colInfo = rightColumns.find((c) => c.column_name === dc.name);
|
||||
const label = dc.label || colInfo?.column_comment?.replace(/\s*\([^)]+\)$/, "") || dc.name;
|
||||
const tableName = dc.sourceTable || config.rightPanel?.tableName || "";
|
||||
const tableLabel = tables.find((t) => t.table_name === tableName)?.table_comment || tableName;
|
||||
const actualColName = dc.name.includes(".") ? dc.name.split(".")[1] : dc.name;
|
||||
|
||||
return (
|
||||
<SelectItem key={`search-${dc.name}-${dcIndex}`} value={dc.name}>
|
||||
<span className="flex flex-col">
|
||||
<span className="flex items-center gap-1">
|
||||
<span>{label}</span>
|
||||
<span className="text-[10px] text-muted-foreground">({tableLabel})</span>
|
||||
</span>
|
||||
<span className="text-[10px] text-muted-foreground">{actualColName}</span>
|
||||
</span>
|
||||
</SelectItem>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-8 w-8 shrink-0 p-0"
|
||||
onClick={() => {
|
||||
const current = config.rightPanel?.searchColumns || [];
|
||||
updateConfig(
|
||||
"rightPanel.searchColumns",
|
||||
current.filter((_, i) => i !== index)
|
||||
);
|
||||
}}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{(config.rightPanel?.displayColumns || []).length === 0 && (
|
||||
<div className="rounded-md border py-3 text-center text-xs text-muted-foreground">
|
||||
먼저 표시할 컬럼을 추가하세요
|
||||
</div>
|
||||
))}
|
||||
{(config.rightPanel?.searchColumns || []).length === 0 && (
|
||||
)}
|
||||
{(config.rightPanel?.displayColumns || []).length > 0 && (config.rightPanel?.searchColumns || []).length === 0 && (
|
||||
<div className="rounded-md border py-3 text-center text-xs text-muted-foreground">
|
||||
검색할 컬럼을 추가하세요
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user