dev #46
|
|
@ -25,6 +25,7 @@ export class EntityJoinController {
|
|||
sortBy,
|
||||
sortOrder = "asc",
|
||||
enableEntityJoin = true,
|
||||
additionalJoinColumns, // 추가 조인 컬럼 정보 (JSON 문자열)
|
||||
userLang, // userLang은 별도로 분리하여 search에 포함되지 않도록 함
|
||||
...otherParams
|
||||
} = req.query;
|
||||
|
|
@ -49,6 +50,21 @@ export class EntityJoinController {
|
|||
}
|
||||
}
|
||||
|
||||
// 추가 조인 컬럼 정보 처리
|
||||
let parsedAdditionalJoinColumns: any[] = [];
|
||||
if (additionalJoinColumns) {
|
||||
try {
|
||||
parsedAdditionalJoinColumns =
|
||||
typeof additionalJoinColumns === "string"
|
||||
? JSON.parse(additionalJoinColumns)
|
||||
: additionalJoinColumns;
|
||||
logger.info("추가 조인 컬럼 파싱 완료:", parsedAdditionalJoinColumns);
|
||||
} catch (error) {
|
||||
logger.warn("추가 조인 컬럼 파싱 오류:", error);
|
||||
parsedAdditionalJoinColumns = [];
|
||||
}
|
||||
}
|
||||
|
||||
const result = await tableManagementService.getTableDataWithEntityJoins(
|
||||
tableName,
|
||||
{
|
||||
|
|
@ -62,6 +78,7 @@ export class EntityJoinController {
|
|||
sortOrder: sortOrder as string,
|
||||
enableEntityJoin:
|
||||
enableEntityJoin === "true" || enableEntityJoin === true,
|
||||
additionalJoinColumns: parsedAdditionalJoinColumns,
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -295,6 +312,124 @@ export class EntityJoinController {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entity 조인된 테이블의 추가 컬럼 목록 조회
|
||||
* GET /api/table-management/tables/:tableName/entity-join-columns
|
||||
*/
|
||||
async getEntityJoinColumns(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { tableName } = req.params;
|
||||
|
||||
logger.info(`Entity 조인 컬럼 조회: ${tableName}`);
|
||||
|
||||
// 1. 현재 테이블의 Entity 조인 설정 조회
|
||||
const joinConfigs = await entityJoinService.detectEntityJoins(tableName);
|
||||
|
||||
if (joinConfigs.length === 0) {
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: "Entity 조인 설정이 없습니다.",
|
||||
data: {
|
||||
tableName,
|
||||
joinTables: [],
|
||||
availableColumns: [],
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 각 조인 테이블의 컬럼 정보 조회
|
||||
const joinTablesInfo = await Promise.all(
|
||||
joinConfigs.map(async (config) => {
|
||||
try {
|
||||
const columns =
|
||||
await tableManagementService.getReferenceTableColumns(
|
||||
config.referenceTable
|
||||
);
|
||||
|
||||
// 현재 display_column으로 사용 중인 컬럼 제외
|
||||
const availableColumns = columns.filter(
|
||||
(col) => col.columnName !== config.displayColumn
|
||||
);
|
||||
|
||||
return {
|
||||
joinConfig: config,
|
||||
tableName: config.referenceTable,
|
||||
currentDisplayColumn: config.displayColumn,
|
||||
availableColumns: availableColumns.map((col) => ({
|
||||
columnName: col.columnName,
|
||||
columnLabel: col.displayName || col.columnName,
|
||||
dataType: col.dataType,
|
||||
isNullable: true, // 기본값으로 설정
|
||||
maxLength: undefined, // 정보가 없으므로 undefined
|
||||
description: col.displayName,
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`참조 테이블 컬럼 조회 실패: ${config.referenceTable}`,
|
||||
error
|
||||
);
|
||||
return {
|
||||
joinConfig: config,
|
||||
tableName: config.referenceTable,
|
||||
currentDisplayColumn: config.displayColumn,
|
||||
availableColumns: [],
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
};
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// 3. 사용 가능한 모든 컬럼 목록 생성 (중복 제거)
|
||||
const allAvailableColumns: Array<{
|
||||
tableName: string;
|
||||
columnName: string;
|
||||
columnLabel: string;
|
||||
dataType: string;
|
||||
joinAlias: string;
|
||||
suggestedLabel: string;
|
||||
}> = [];
|
||||
|
||||
joinTablesInfo.forEach((info) => {
|
||||
info.availableColumns.forEach((col) => {
|
||||
const joinAlias = `${info.joinConfig.sourceColumn}_${col.columnName}`;
|
||||
const suggestedLabel = col.columnLabel; // 라벨명만 사용
|
||||
|
||||
allAvailableColumns.push({
|
||||
tableName: info.tableName,
|
||||
columnName: col.columnName,
|
||||
columnLabel: col.columnLabel,
|
||||
dataType: col.dataType,
|
||||
joinAlias,
|
||||
suggestedLabel,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: "Entity 조인 컬럼 조회 성공",
|
||||
data: {
|
||||
tableName,
|
||||
joinTables: joinTablesInfo,
|
||||
availableColumns: allAvailableColumns,
|
||||
summary: {
|
||||
totalJoinTables: joinConfigs.length,
|
||||
totalAvailableColumns: allAvailableColumns.length,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Entity 조인 컬럼 조회 실패", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "Entity 조인 컬럼 조회 중 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 공통 참조 테이블 자동 캐싱
|
||||
* POST /api/table-management/cache/preload
|
||||
|
|
|
|||
|
|
@ -120,6 +120,71 @@ router.put(
|
|||
// 🎯 참조 테이블 정보
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Entity 조인된 테이블의 추가 컬럼 목록 조회 (화면편집기용)
|
||||
* GET /api/table-management/tables/:tableName/entity-join-columns
|
||||
*
|
||||
* 특정 테이블에 설정된 모든 Entity 조인의 참조 테이블들에서
|
||||
* 추가로 표시할 수 있는 컬럼들의 목록을 반환합니다.
|
||||
*
|
||||
* Response:
|
||||
* {
|
||||
* success: true,
|
||||
* data: {
|
||||
* tableName: "companies",
|
||||
* joinTables: [
|
||||
* {
|
||||
* joinConfig: { sourceColumn: "writer", referenceTable: "user_info", ... },
|
||||
* tableName: "user_info",
|
||||
* currentDisplayColumn: "user_name",
|
||||
* availableColumns: [
|
||||
* {
|
||||
* columnName: "email",
|
||||
* columnLabel: "이메일",
|
||||
* dataType: "character varying",
|
||||
* isNullable: true,
|
||||
* description: "사용자 이메일"
|
||||
* },
|
||||
* {
|
||||
* columnName: "dept_code",
|
||||
* columnLabel: "부서코드",
|
||||
* dataType: "character varying",
|
||||
* isNullable: false,
|
||||
* description: "소속 부서"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* ],
|
||||
* availableColumns: [
|
||||
* {
|
||||
* tableName: "user_info",
|
||||
* columnName: "email",
|
||||
* columnLabel: "이메일",
|
||||
* dataType: "character varying",
|
||||
* joinAlias: "writer_email",
|
||||
* suggestedLabel: "writer (이메일)"
|
||||
* },
|
||||
* {
|
||||
* tableName: "user_info",
|
||||
* columnName: "dept_code",
|
||||
* columnLabel: "부서코드",
|
||||
* dataType: "character varying",
|
||||
* joinAlias: "writer_dept_code",
|
||||
* suggestedLabel: "writer (부서코드)"
|
||||
* }
|
||||
* ],
|
||||
* summary: {
|
||||
* totalJoinTables: 1,
|
||||
* totalAvailableColumns: 2
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
router.get(
|
||||
"/tables/:tableName/entity-join-columns",
|
||||
entityJoinController.getEntityJoinColumns.bind(entityJoinController)
|
||||
);
|
||||
|
||||
/**
|
||||
* 참조 테이블의 표시 가능한 컬럼 목록 조회
|
||||
* GET /api/table-management/reference-tables/:tableName/columns
|
||||
|
|
|
|||
|
|
@ -93,11 +93,11 @@ export class EntityJoinService {
|
|||
// 기본 SELECT 컬럼들
|
||||
const baseColumns = selectColumns.map((col) => `main.${col}`).join(", ");
|
||||
|
||||
// Entity 조인 컬럼들
|
||||
// Entity 조인 컬럼들 (COALESCE로 NULL을 빈 문자열로 처리)
|
||||
const joinColumns = joinConfigs
|
||||
.map(
|
||||
(config) =>
|
||||
`${config.referenceTable.substring(0, 3)}.${config.displayColumn} AS ${config.aliasColumn}`
|
||||
`COALESCE(${config.referenceTable.substring(0, 3)}.${config.displayColumn}, '') AS ${config.aliasColumn}`
|
||||
)
|
||||
.join(", ");
|
||||
|
||||
|
|
|
|||
|
|
@ -1412,6 +1412,11 @@ export class TableManagementService {
|
|||
sortBy?: string;
|
||||
sortOrder?: string;
|
||||
enableEntityJoin?: boolean;
|
||||
additionalJoinColumns?: Array<{
|
||||
sourceTable: string;
|
||||
sourceColumn: string;
|
||||
joinAlias: string;
|
||||
}>;
|
||||
}
|
||||
): Promise<EntityJoinResponse> {
|
||||
const startTime = Date.now();
|
||||
|
|
@ -1432,7 +1437,41 @@ export class TableManagementService {
|
|||
}
|
||||
|
||||
// Entity 조인 설정 감지
|
||||
const joinConfigs = await entityJoinService.detectEntityJoins(tableName);
|
||||
let joinConfigs = await entityJoinService.detectEntityJoins(tableName);
|
||||
|
||||
// 추가 조인 컬럼 정보가 있으면 조인 설정에 추가
|
||||
if (
|
||||
options.additionalJoinColumns &&
|
||||
options.additionalJoinColumns.length > 0
|
||||
) {
|
||||
logger.info(
|
||||
`추가 조인 컬럼 처리: ${options.additionalJoinColumns.length}개`
|
||||
);
|
||||
|
||||
for (const additionalColumn of options.additionalJoinColumns) {
|
||||
// 기존 조인 설정에서 같은 참조 테이블을 사용하는 설정 찾기
|
||||
const baseJoinConfig = joinConfigs.find(
|
||||
(config) => config.referenceTable === additionalColumn.sourceTable
|
||||
);
|
||||
|
||||
if (baseJoinConfig) {
|
||||
// 추가 조인 컬럼 설정 생성
|
||||
const additionalJoinConfig: EntityJoinConfig = {
|
||||
sourceTable: tableName,
|
||||
sourceColumn: baseJoinConfig.sourceColumn, // 원본 컬럼 (writer)
|
||||
referenceTable: additionalColumn.sourceTable, // 참조 테이블 (user_info)
|
||||
referenceColumn: baseJoinConfig.referenceColumn, // 참조 키 (user_id)
|
||||
displayColumn: additionalColumn.sourceColumn, // 표시할 컬럼 (email)
|
||||
aliasColumn: additionalColumn.joinAlias, // 별칭 (writer_email)
|
||||
};
|
||||
|
||||
joinConfigs.push(additionalJoinConfig);
|
||||
logger.info(
|
||||
`추가 조인 컬럼 설정 추가: ${additionalJoinConfig.aliasColumn}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (joinConfigs.length === 0) {
|
||||
logger.info(`Entity 조인 설정이 없음: ${tableName}`);
|
||||
|
|
@ -1624,7 +1663,11 @@ export class TableManagementService {
|
|||
String(sourceValue)
|
||||
);
|
||||
|
||||
enhancedRow[config.aliasColumn] = lookupValue || sourceValue;
|
||||
// null이나 undefined인 경우 빈 문자열로 설정
|
||||
enhancedRow[config.aliasColumn] = lookupValue || "";
|
||||
} else {
|
||||
// sourceValue가 없는 경우도 빈 문자열로 설정
|
||||
enhancedRow[config.aliasColumn] = "";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1946,11 +1989,18 @@ export class TableManagementService {
|
|||
const keyValue = row[config.sourceColumn];
|
||||
if (keyValue) {
|
||||
const lookupValue = cachedData.get(String(keyValue));
|
||||
if (lookupValue) {
|
||||
row[config.aliasColumn] = lookupValue;
|
||||
}
|
||||
// null이나 undefined인 경우 빈 문자열로 설정
|
||||
row[config.aliasColumn] = lookupValue || "";
|
||||
} else {
|
||||
// sourceValue가 없는 경우도 빈 문자열로 설정
|
||||
row[config.aliasColumn] = "";
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 캐시가 없는 경우 모든 행에 빈 문자열 설정
|
||||
enhancedData.forEach((row) => {
|
||||
row[config.aliasColumn] = "";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,11 @@ export const entityJoinApi = {
|
|||
sortBy?: string;
|
||||
sortOrder?: "asc" | "desc";
|
||||
enableEntityJoin?: boolean;
|
||||
additionalJoinColumns?: Array<{
|
||||
sourceTable: string;
|
||||
sourceColumn: string;
|
||||
joinAlias: string;
|
||||
}>;
|
||||
} = {},
|
||||
): Promise<EntityJoinResponse> => {
|
||||
const searchParams = new URLSearchParams();
|
||||
|
|
@ -87,6 +92,7 @@ export const entityJoinApi = {
|
|||
params: {
|
||||
...params,
|
||||
search: params.search ? JSON.stringify(params.search) : undefined,
|
||||
additionalJoinColumns: params.additionalJoinColumns ? JSON.stringify(params.additionalJoinColumns) : undefined,
|
||||
},
|
||||
});
|
||||
return response.data.data;
|
||||
|
|
@ -153,6 +159,40 @@ export const entityJoinApi = {
|
|||
await apiClient.delete(`/table-management/cache`, { params });
|
||||
},
|
||||
|
||||
/**
|
||||
* Entity 조인된 테이블의 추가 컬럼 목록 조회 (화면편집기용)
|
||||
*/
|
||||
getEntityJoinColumns: async (
|
||||
tableName: string,
|
||||
): Promise<{
|
||||
tableName: string;
|
||||
joinTables: Array<{
|
||||
tableName: string;
|
||||
currentDisplayColumn: string;
|
||||
availableColumns: Array<{
|
||||
columnName: string;
|
||||
columnLabel: string;
|
||||
dataType: string;
|
||||
description?: string;
|
||||
}>;
|
||||
}>;
|
||||
availableColumns: Array<{
|
||||
tableName: string;
|
||||
columnName: string;
|
||||
columnLabel: string;
|
||||
dataType: string;
|
||||
joinAlias: string;
|
||||
suggestedLabel: string;
|
||||
}>;
|
||||
summary: {
|
||||
totalJoinTables: number;
|
||||
totalAvailableColumns: number;
|
||||
};
|
||||
}> => {
|
||||
const response = await apiClient.get(`/table-management/tables/${tableName}/entity-join-columns`);
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* 공통 참조 테이블 자동 캐싱
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -206,6 +206,16 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
// 🎯 Entity 조인 API 사용 - Entity 조인이 포함된 데이터 조회
|
||||
console.log("🔗 Entity 조인 데이터 조회 시작:", tableConfig.selectedTable);
|
||||
|
||||
// Entity 조인 컬럼 추출 (isEntityJoin === true인 컬럼들)
|
||||
const entityJoinColumns = tableConfig.columns?.filter((col) => col.isEntityJoin && col.entityJoinInfo) || [];
|
||||
const additionalJoinColumns = entityJoinColumns.map((col) => ({
|
||||
sourceTable: col.entityJoinInfo!.sourceTable,
|
||||
sourceColumn: col.entityJoinInfo!.sourceColumn,
|
||||
joinAlias: col.entityJoinInfo!.joinAlias,
|
||||
}));
|
||||
|
||||
console.log("🔗 추가 Entity 조인 컬럼:", additionalJoinColumns);
|
||||
|
||||
const result = await entityJoinApi.getTableDataWithJoins(tableConfig.selectedTable, {
|
||||
page: currentPage,
|
||||
size: localPageSize,
|
||||
|
|
@ -262,6 +272,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
sortBy: sortColumn || undefined,
|
||||
sortOrder: sortDirection,
|
||||
enableEntityJoin: true, // 🎯 Entity 조인 활성화
|
||||
additionalJoinColumns: additionalJoinColumns.length > 0 ? additionalJoinColumns : undefined, // 추가 조인 컬럼
|
||||
});
|
||||
|
||||
if (result) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { Badge } from "@/components/ui/badge";
|
|||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { TableListConfig, ColumnConfig } from "./types";
|
||||
import { entityJoinApi } from "@/lib/api/entityJoin";
|
||||
import {
|
||||
Plus,
|
||||
Trash2,
|
||||
|
|
@ -50,6 +51,27 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
const [availableColumns, setAvailableColumns] = useState<
|
||||
Array<{ columnName: string; dataType: string; label?: string }>
|
||||
>([]);
|
||||
const [entityJoinColumns, setEntityJoinColumns] = useState<{
|
||||
availableColumns: Array<{
|
||||
tableName: string;
|
||||
columnName: string;
|
||||
columnLabel: string;
|
||||
dataType: string;
|
||||
joinAlias: string;
|
||||
suggestedLabel: string;
|
||||
}>;
|
||||
joinTables: Array<{
|
||||
tableName: string;
|
||||
currentDisplayColumn: string;
|
||||
availableColumns: Array<{
|
||||
columnName: string;
|
||||
columnLabel: string;
|
||||
dataType: string;
|
||||
description?: string;
|
||||
}>;
|
||||
}>;
|
||||
}>({ availableColumns: [], joinTables: [] });
|
||||
const [loadingEntityJoins, setLoadingEntityJoins] = useState(false);
|
||||
|
||||
// 화면 테이블명이 있으면 자동으로 설정
|
||||
useEffect(() => {
|
||||
|
|
@ -137,6 +159,36 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
}
|
||||
}, [config.selectedTable, screenTableName, tableColumns]);
|
||||
|
||||
// Entity 조인 컬럼 정보 가져오기
|
||||
useEffect(() => {
|
||||
const fetchEntityJoinColumns = async () => {
|
||||
const tableName = config.selectedTable || screenTableName;
|
||||
if (!tableName) {
|
||||
setEntityJoinColumns({ availableColumns: [], joinTables: [] });
|
||||
return;
|
||||
}
|
||||
|
||||
setLoadingEntityJoins(true);
|
||||
try {
|
||||
console.log("🔗 Entity 조인 컬럼 정보 가져오기:", tableName);
|
||||
const result = await entityJoinApi.getEntityJoinColumns(tableName);
|
||||
console.log("✅ Entity 조인 컬럼 응답:", result);
|
||||
|
||||
setEntityJoinColumns({
|
||||
availableColumns: result.availableColumns || [],
|
||||
joinTables: result.joinTables || [],
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("❌ Entity 조인 컬럼 조회 오류:", error);
|
||||
setEntityJoinColumns({ availableColumns: [], joinTables: [] });
|
||||
} finally {
|
||||
setLoadingEntityJoins(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchEntityJoinColumns();
|
||||
}, [config.selectedTable, screenTableName]);
|
||||
|
||||
const handleChange = (key: keyof TableListConfig, value: any) => {
|
||||
onChange({ [key]: value });
|
||||
};
|
||||
|
|
@ -176,6 +228,32 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
handleChange("columns", [...(config.columns || []), newColumn]);
|
||||
};
|
||||
|
||||
// Entity 조인 컬럼 추가
|
||||
const addEntityJoinColumn = (joinColumn: (typeof entityJoinColumns.availableColumns)[0]) => {
|
||||
const existingColumn = config.columns?.find((col) => col.columnName === joinColumn.joinAlias);
|
||||
if (existingColumn) return;
|
||||
|
||||
const newColumn: ColumnConfig = {
|
||||
columnName: joinColumn.joinAlias,
|
||||
displayName: joinColumn.columnLabel, // 라벨명만 사용
|
||||
visible: true,
|
||||
sortable: true,
|
||||
searchable: true,
|
||||
align: "left",
|
||||
format: "text",
|
||||
order: config.columns?.length || 0,
|
||||
isEntityJoin: true, // Entity 조인 컬럼임을 표시
|
||||
entityJoinInfo: {
|
||||
sourceTable: joinColumn.tableName,
|
||||
sourceColumn: joinColumn.columnName,
|
||||
joinAlias: joinColumn.joinAlias,
|
||||
},
|
||||
};
|
||||
|
||||
handleChange("columns", [...(config.columns || []), newColumn]);
|
||||
console.log("🔗 Entity 조인 컬럼 추가됨:", newColumn);
|
||||
};
|
||||
|
||||
// 컬럼 제거
|
||||
const removeColumn = (columnName: string) => {
|
||||
const updatedColumns = config.columns?.filter((col) => col.columnName !== columnName) || [];
|
||||
|
|
@ -214,7 +292,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<div className="text-sm font-medium">테이블 리스트 설정</div>
|
||||
|
||||
<Tabs defaultValue="basic" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-5">
|
||||
<TabsList className="grid w-full grid-cols-6">
|
||||
<TabsTrigger value="basic" className="flex items-center gap-1">
|
||||
<Settings className="h-3 w-3" />
|
||||
기본
|
||||
|
|
@ -223,6 +301,10 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<Columns className="h-3 w-3" />
|
||||
컬럼
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="join-columns" className="flex items-center gap-1">
|
||||
<Plus className="h-3 w-3" />
|
||||
조인
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="filter" className="flex items-center gap-1">
|
||||
<Filter className="h-3 w-3" />
|
||||
필터
|
||||
|
|
@ -610,6 +692,127 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
)}
|
||||
</TabsContent>
|
||||
|
||||
{/* Entity 조인 컬럼 추가 탭 */}
|
||||
<TabsContent value="join-columns" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Entity 조인 컬럼 추가</CardTitle>
|
||||
<CardDescription>Entity 조인된 테이블의 다른 컬럼들을 추가로 표시할 수 있습니다.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<ScrollArea className="max-h-96 pr-4">
|
||||
{loadingEntityJoins ? (
|
||||
<div className="text-muted-foreground py-4 text-center">조인 정보를 가져오는 중...</div>
|
||||
) : entityJoinColumns.joinTables.length === 0 ? (
|
||||
<div className="text-muted-foreground py-8 text-center">
|
||||
<div className="text-sm">Entity 조인이 설정된 컬럼이 없습니다.</div>
|
||||
<div className="mt-1 text-xs">
|
||||
먼저 컬럼의 웹타입을 'entity'로 설정하고 참조 테이블을 지정해주세요.
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{/* 조인 테이블별 그룹 */}
|
||||
{entityJoinColumns.joinTables.map((joinTable, tableIndex) => (
|
||||
<Card key={tableIndex} className="border-l-4 border-l-blue-500">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2 text-sm">
|
||||
📊 {joinTable.tableName}
|
||||
<Badge variant="outline" className="text-xs">
|
||||
현재: {joinTable.currentDisplayColumn}
|
||||
</Badge>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
{joinTable.availableColumns.length === 0 ? (
|
||||
<div className="text-muted-foreground py-2 text-sm">추가할 수 있는 컬럼이 없습니다.</div>
|
||||
) : (
|
||||
<div className="grid gap-2">
|
||||
{joinTable.availableColumns.map((column, colIndex) => {
|
||||
const matchingJoinColumn = entityJoinColumns.availableColumns.find(
|
||||
(jc) => jc.tableName === joinTable.tableName && jc.columnName === column.columnName,
|
||||
);
|
||||
|
||||
const isAlreadyAdded = config.columns?.some(
|
||||
(col) => col.columnName === matchingJoinColumn?.joinAlias,
|
||||
);
|
||||
|
||||
return (
|
||||
<div key={colIndex} className="flex items-center justify-between rounded border p-2">
|
||||
<div className="flex-1">
|
||||
<div className="text-sm font-medium">{column.columnLabel}</div>
|
||||
<div className="text-muted-foreground text-xs">
|
||||
{column.columnName} ({column.dataType})
|
||||
</div>
|
||||
{column.description && (
|
||||
<div className="text-muted-foreground mt-1 text-xs">{column.description}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{isAlreadyAdded ? (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
추가됨
|
||||
</Badge>
|
||||
) : (
|
||||
matchingJoinColumn && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => addEntityJoinColumn(matchingJoinColumn)}
|
||||
className="text-xs"
|
||||
>
|
||||
<Plus className="mr-1 h-3 w-3" />
|
||||
추가
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
|
||||
{/* 전체 사용 가능한 컬럼 요약 */}
|
||||
{entityJoinColumns.availableColumns.length > 0 && (
|
||||
<Card className="bg-muted/30">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm">📋 추가 가능한 컬럼 요약</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<div className="text-muted-foreground mb-2 text-sm">
|
||||
총 {entityJoinColumns.availableColumns.length}개의 컬럼을 추가할 수 있습니다.
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{entityJoinColumns.availableColumns.map((column, index) => {
|
||||
const isAlreadyAdded = config.columns?.some((col) => col.columnName === column.joinAlias);
|
||||
|
||||
return (
|
||||
<Badge
|
||||
key={index}
|
||||
variant={isAlreadyAdded ? "secondary" : "outline"}
|
||||
className="cursor-pointer text-xs"
|
||||
onClick={() => !isAlreadyAdded && addEntityJoinColumn(column)}
|
||||
>
|
||||
{column.columnLabel}
|
||||
{!isAlreadyAdded && <Plus className="ml-1 h-2 w-2" />}
|
||||
</Badge>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
{/* 필터 설정 탭 */}
|
||||
<TabsContent value="filter" className="space-y-4">
|
||||
<Card>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,15 @@
|
|||
|
||||
import { ComponentConfig } from "@/types/component";
|
||||
|
||||
/**
|
||||
* Entity 조인 정보
|
||||
*/
|
||||
export interface EntityJoinInfo {
|
||||
sourceTable: string;
|
||||
sourceColumn: string;
|
||||
joinAlias: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블 컬럼 설정
|
||||
*/
|
||||
|
|
@ -16,7 +25,8 @@ export interface ColumnConfig {
|
|||
format?: "text" | "number" | "date" | "currency" | "boolean";
|
||||
order: number;
|
||||
dataType?: string; // 컬럼 데이터 타입 (검색 컬럼 선택에 사용)
|
||||
isEntityJoined?: boolean; // 🎯 Entity 조인된 컬럼인지 여부
|
||||
isEntityJoin?: boolean; // Entity 조인된 컬럼인지 여부
|
||||
entityJoinInfo?: EntityJoinInfo; // Entity 조인 상세 정보
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue