리피터 케이블 설정 구현
This commit is contained in:
@@ -18,6 +18,9 @@ interface ComponentsPanelProps {
|
||||
onTableDragStart?: (e: React.DragEvent, table: TableInfo, column?: ColumnInfo) => void;
|
||||
selectedTableName?: string;
|
||||
placedColumns?: Set<string>; // 이미 배치된 컬럼명 집합
|
||||
// 테이블 선택 관련 props
|
||||
onTableSelect?: (tableName: string) => void; // 테이블 선택 콜백
|
||||
showTableSelector?: boolean; // 테이블 선택 UI 표시 여부 (기본: 테이블 없으면 표시)
|
||||
}
|
||||
|
||||
export function ComponentsPanel({
|
||||
@@ -28,6 +31,8 @@ export function ComponentsPanel({
|
||||
onTableDragStart,
|
||||
selectedTableName,
|
||||
placedColumns,
|
||||
onTableSelect,
|
||||
showTableSelector = true,
|
||||
}: ComponentsPanelProps) {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
@@ -272,24 +277,16 @@ export function ComponentsPanel({
|
||||
|
||||
{/* 테이블 컬럼 탭 */}
|
||||
<TabsContent value="tables" className="mt-0 flex-1 overflow-y-auto">
|
||||
{tables.length > 0 && onTableDragStart ? (
|
||||
<TablesPanel
|
||||
tables={tables}
|
||||
searchTerm={searchTerm}
|
||||
onSearchChange={onSearchChange || (() => {})}
|
||||
onDragStart={onTableDragStart}
|
||||
selectedTableName={selectedTableName}
|
||||
placedColumns={placedColumns}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-32 items-center justify-center text-center">
|
||||
<div className="p-6">
|
||||
<Database className="text-muted-foreground/40 mx-auto mb-2 h-10 w-10" />
|
||||
<p className="text-muted-foreground text-xs font-medium">테이블이 없습니다</p>
|
||||
<p className="text-muted-foreground/60 mt-1 text-xs">화면에 테이블을 선택해주세요</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<TablesPanel
|
||||
tables={tables}
|
||||
searchTerm={searchTerm}
|
||||
onSearchChange={onSearchChange || (() => {})}
|
||||
onDragStart={onTableDragStart || (() => {})}
|
||||
selectedTableName={selectedTableName}
|
||||
placedColumns={placedColumns}
|
||||
onTableSelect={onTableSelect}
|
||||
showTableSelector={showTableSelector}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
{/* 컴포넌트 탭 */}
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
Database,
|
||||
Type,
|
||||
@@ -16,9 +24,13 @@ import {
|
||||
Link2,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Plus,
|
||||
Search,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { TableInfo, WebType } from "@/types/screen";
|
||||
import { entityJoinApi } from "@/lib/api/entityJoin";
|
||||
import { tableManagementApi } from "@/lib/api/tableManagement";
|
||||
|
||||
interface EntityJoinColumn {
|
||||
columnName: string;
|
||||
@@ -41,6 +53,9 @@ interface TablesPanelProps {
|
||||
onDragStart: (e: React.DragEvent, table: TableInfo, column?: any) => void;
|
||||
selectedTableName?: string;
|
||||
placedColumns?: Set<string>; // 이미 배치된 컬럼명 집합 (tableName.columnName 형식)
|
||||
// 테이블 선택 관련 props
|
||||
onTableSelect?: (tableName: string) => void; // 테이블 선택 콜백
|
||||
showTableSelector?: boolean; // 테이블 선택 UI 표시 여부
|
||||
}
|
||||
|
||||
// 위젯 타입별 아이콘
|
||||
@@ -81,12 +96,20 @@ export const TablesPanel: React.FC<TablesPanelProps> = ({
|
||||
searchTerm,
|
||||
onDragStart,
|
||||
placedColumns = new Set(),
|
||||
onTableSelect,
|
||||
showTableSelector = false,
|
||||
}) => {
|
||||
// 엔티티 조인 컬럼 상태
|
||||
const [entityJoinTables, setEntityJoinTables] = useState<EntityJoinTable[]>([]);
|
||||
const [loadingEntityJoins, setLoadingEntityJoins] = useState(false);
|
||||
const [expandedJoinTables, setExpandedJoinTables] = useState<Set<string>>(new Set());
|
||||
|
||||
// 전체 테이블 목록 (테이블 선택용)
|
||||
const [allTables, setAllTables] = useState<Array<{ tableName: string; displayName: string }>>([]);
|
||||
const [loadingAllTables, setLoadingAllTables] = useState(false);
|
||||
const [tableSearchTerm, setTableSearchTerm] = useState("");
|
||||
const [showTableSelectDropdown, setShowTableSelectDropdown] = useState(false);
|
||||
|
||||
// 시스템 컬럼 목록 (숨김 처리)
|
||||
const systemColumns = new Set([
|
||||
"id",
|
||||
@@ -96,6 +119,42 @@ export const TablesPanel: React.FC<TablesPanelProps> = ({
|
||||
"company_code",
|
||||
]);
|
||||
|
||||
// 전체 테이블 목록 로드
|
||||
const loadAllTables = useCallback(async () => {
|
||||
if (allTables.length > 0) return; // 이미 로드됨
|
||||
|
||||
setLoadingAllTables(true);
|
||||
try {
|
||||
const response = await tableManagementApi.getTableList();
|
||||
if (response.success && response.data) {
|
||||
setAllTables(response.data.map((t: any) => ({
|
||||
tableName: t.tableName || t.table_name,
|
||||
displayName: t.displayName || t.table_label || t.tableName || t.table_name,
|
||||
})));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("테이블 목록 조회 오류:", error);
|
||||
} finally {
|
||||
setLoadingAllTables(false);
|
||||
}
|
||||
}, [allTables.length]);
|
||||
|
||||
// 테이블 선택 시 호출
|
||||
const handleTableSelect = (tableName: string) => {
|
||||
setShowTableSelectDropdown(false);
|
||||
setTableSearchTerm("");
|
||||
onTableSelect?.(tableName);
|
||||
};
|
||||
|
||||
// 필터링된 테이블 목록
|
||||
const filteredAllTables = tableSearchTerm
|
||||
? allTables.filter(
|
||||
(t) =>
|
||||
t.tableName.toLowerCase().includes(tableSearchTerm.toLowerCase()) ||
|
||||
t.displayName.toLowerCase().includes(tableSearchTerm.toLowerCase())
|
||||
)
|
||||
: allTables;
|
||||
|
||||
// 메인 테이블명 추출
|
||||
const mainTableName = tables[0]?.tableName;
|
||||
|
||||
@@ -209,6 +268,91 @@ export const TablesPanel: React.FC<TablesPanelProps> = ({
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
{/* 테이블 선택 버튼 (메인 테이블이 없을 때 또는 showTableSelector가 true일 때) */}
|
||||
{(showTableSelector || tables.length === 0) && (
|
||||
<div className="border-b p-3">
|
||||
<div className="relative">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full justify-between"
|
||||
onClick={() => {
|
||||
setShowTableSelectDropdown(!showTableSelectDropdown);
|
||||
if (!showTableSelectDropdown) {
|
||||
loadAllTables();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
{tables.length > 0 ? "테이블 추가/변경" : "테이블 선택"}
|
||||
</span>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
{/* 드롭다운 */}
|
||||
{showTableSelectDropdown && (
|
||||
<div className="absolute top-full left-0 z-50 mt-1 w-full rounded-md border bg-white shadow-lg">
|
||||
{/* 검색 */}
|
||||
<div className="border-b p-2">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="테이블 검색..."
|
||||
value={tableSearchTerm}
|
||||
onChange={(e) => setTableSearchTerm(e.target.value)}
|
||||
autoFocus
|
||||
className="w-full rounded-md border px-8 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
{tableSearchTerm && (
|
||||
<button
|
||||
onClick={() => setTableSearchTerm("")}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2"
|
||||
>
|
||||
<X className="h-3.5 w-3.5 text-gray-400" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 테이블 목록 */}
|
||||
<div className="max-h-60 overflow-y-auto">
|
||||
{loadingAllTables ? (
|
||||
<div className="p-4 text-center text-sm text-gray-500">로드 중...</div>
|
||||
) : filteredAllTables.length === 0 ? (
|
||||
<div className="p-4 text-center text-sm text-gray-500">
|
||||
{tableSearchTerm ? "검색 결과 없음" : "테이블 없음"}
|
||||
</div>
|
||||
) : (
|
||||
filteredAllTables.map((t) => (
|
||||
<button
|
||||
key={t.tableName}
|
||||
onClick={() => handleTableSelect(t.tableName)}
|
||||
className="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-gray-100"
|
||||
>
|
||||
<Database className="h-3.5 w-3.5 text-blue-600" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate font-medium">{t.displayName}</div>
|
||||
<div className="truncate text-xs text-gray-500">{t.tableName}</div>
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 현재 테이블 정보 */}
|
||||
{tables.length > 0 && (
|
||||
<div className="mt-2 text-xs text-muted-foreground">
|
||||
현재: {tables[0]?.tableLabel || tables[0]?.tableName}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 테이블과 컬럼 평면 목록 */}
|
||||
<div className="flex-1 overflow-y-auto p-3">
|
||||
<div className="space-y-2">
|
||||
|
||||
Reference in New Issue
Block a user