조인테이블의 컬럼 사용할 수 있도록 수정

This commit is contained in:
kjs
2025-09-16 18:02:19 +09:00
parent 1d05965a55
commit 049d8ed295
8 changed files with 523 additions and 9 deletions

View File

@@ -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;
},
/**
* 공통 참조 테이블 자동 캐싱
*/

View File

@@ -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) {

View File

@@ -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>

View File

@@ -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 조인 상세 정보
}
/**