분할 패널 및 반복 필드 그룹 컴포넌트
This commit is contained in:
@@ -12,7 +12,8 @@ import { Button } from "@/components/ui/button";
|
||||
import { Check, ChevronsUpDown, ArrowRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { SplitPanelLayoutConfig } from "./types";
|
||||
import { TableInfo } from "@/types/screen";
|
||||
import { TableInfo, ColumnInfo } from "@/types/screen";
|
||||
import { tableTypeApi } from "@/lib/api/screen";
|
||||
|
||||
interface SplitPanelLayoutConfigPanelProps {
|
||||
config: SplitPanelLayoutConfig;
|
||||
@@ -33,24 +34,71 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
|
||||
const [rightTableOpen, setRightTableOpen] = useState(false);
|
||||
const [leftColumnOpen, setLeftColumnOpen] = useState(false);
|
||||
const [rightColumnOpen, setRightColumnOpen] = useState(false);
|
||||
const [loadedTableColumns, setLoadedTableColumns] = useState<Record<string, ColumnInfo[]>>({});
|
||||
const [loadingColumns, setLoadingColumns] = useState<Record<string, boolean>>({});
|
||||
|
||||
// screenTableName이 변경되면 leftPanel.tableName 자동 업데이트
|
||||
// screenTableName이 변경되면 좌측 패널 테이블을 항상 화면 테이블로 설정
|
||||
useEffect(() => {
|
||||
if (screenTableName) {
|
||||
// 좌측 패널 테이블명 업데이트
|
||||
// 좌측 패널은 항상 현재 화면의 테이블 사용
|
||||
if (config.leftPanel?.tableName !== screenTableName) {
|
||||
updateLeftPanel({ tableName: screenTableName });
|
||||
}
|
||||
|
||||
// 관계 타입이 detail이면 우측 패널도 동일한 테이블 사용
|
||||
const relationshipType = config.rightPanel?.relation?.type || "detail";
|
||||
if (relationshipType === "detail" && config.rightPanel?.tableName !== screenTableName) {
|
||||
updateRightPanel({ tableName: screenTableName });
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [screenTableName]);
|
||||
|
||||
// 테이블 컬럼 로드 함수
|
||||
const loadTableColumns = async (tableName: string) => {
|
||||
if (loadedTableColumns[tableName] || loadingColumns[tableName]) {
|
||||
return; // 이미 로드되었거나 로딩 중
|
||||
}
|
||||
|
||||
setLoadingColumns((prev) => ({ ...prev, [tableName]: true }));
|
||||
|
||||
try {
|
||||
const columnsResponse = await tableTypeApi.getColumns(tableName);
|
||||
console.log(`📊 테이블 ${tableName} 컬럼 응답:`, columnsResponse);
|
||||
|
||||
const columns: ColumnInfo[] = (columnsResponse || []).map((col: any) => ({
|
||||
tableName: col.tableName || tableName,
|
||||
columnName: col.columnName || col.column_name,
|
||||
columnLabel: col.displayName || col.columnLabel || col.column_label || col.columnName || col.column_name,
|
||||
dataType: col.dataType || col.data_type || col.dbType,
|
||||
webType: col.webType || col.web_type,
|
||||
input_type: col.inputType || col.input_type,
|
||||
widgetType: col.widgetType || col.widget_type || col.webType || col.web_type,
|
||||
isNullable: col.isNullable || col.is_nullable,
|
||||
required: col.required !== undefined ? col.required : col.isNullable === "NO" || col.is_nullable === "NO",
|
||||
columnDefault: col.columnDefault || col.column_default,
|
||||
characterMaximumLength: col.characterMaximumLength || col.character_maximum_length,
|
||||
codeCategory: col.codeCategory || col.code_category,
|
||||
codeValue: col.codeValue || col.code_value,
|
||||
}));
|
||||
|
||||
console.log(`✅ 테이블 ${tableName} 컬럼 ${columns.length}개 로드됨:`, columns);
|
||||
setLoadedTableColumns((prev) => ({ ...prev, [tableName]: columns }));
|
||||
} catch (error) {
|
||||
console.error(`테이블 ${tableName} 컬럼 로드 실패:`, error);
|
||||
setLoadedTableColumns((prev) => ({ ...prev, [tableName]: [] }));
|
||||
} finally {
|
||||
setLoadingColumns((prev) => ({ ...prev, [tableName]: false }));
|
||||
}
|
||||
};
|
||||
|
||||
// 좌측/우측 테이블이 변경되면 해당 테이블의 컬럼 로드
|
||||
useEffect(() => {
|
||||
if (config.leftPanel?.tableName) {
|
||||
loadTableColumns(config.leftPanel.tableName);
|
||||
}
|
||||
}, [config.leftPanel?.tableName]);
|
||||
|
||||
useEffect(() => {
|
||||
if (config.rightPanel?.tableName) {
|
||||
loadTableColumns(config.rightPanel.tableName);
|
||||
}
|
||||
}, [config.rightPanel?.tableName]);
|
||||
|
||||
console.log("🔧 SplitPanelLayoutConfigPanel 렌더링");
|
||||
console.log(" - config:", config);
|
||||
console.log(" - tables:", tables);
|
||||
@@ -83,25 +131,24 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
|
||||
onChange(newConfig);
|
||||
};
|
||||
|
||||
// 좌측 테이블은 현재 화면의 테이블 (screenTableName) 사용
|
||||
// 좌측 테이블 컬럼 (로드된 컬럼 사용)
|
||||
const leftTableColumns = useMemo(() => {
|
||||
const tableName = screenTableName || config.leftPanel?.tableName;
|
||||
const table = tables.find((t) => t.tableName === tableName);
|
||||
return table?.columns || [];
|
||||
}, [tables, screenTableName, config.leftPanel?.tableName]);
|
||||
const tableName = config.leftPanel?.tableName || screenTableName;
|
||||
return tableName ? loadedTableColumns[tableName] || [] : [];
|
||||
}, [loadedTableColumns, config.leftPanel?.tableName, screenTableName]);
|
||||
|
||||
// 우측 테이블의 컬럼 목록 가져오기
|
||||
// 우측 테이블 컬럼 (로드된 컬럼 사용)
|
||||
const rightTableColumns = useMemo(() => {
|
||||
const table = tables.find((t) => t.tableName === config.rightPanel?.tableName);
|
||||
return table?.columns || [];
|
||||
}, [tables, config.rightPanel?.tableName]);
|
||||
const tableName = config.rightPanel?.tableName;
|
||||
return tableName ? loadedTableColumns[tableName] || [] : [];
|
||||
}, [loadedTableColumns, config.rightPanel?.tableName]);
|
||||
|
||||
// 테이블 데이터 로딩 상태 확인
|
||||
if (!tables || tables.length === 0) {
|
||||
return (
|
||||
<div className="rounded-lg border border-yellow-200 bg-yellow-50 p-4">
|
||||
<p className="text-sm text-yellow-800">⚠️ 테이블 데이터를 불러올 수 없습니다.</p>
|
||||
<p className="mt-1 text-xs text-yellow-600">
|
||||
<div className="rounded-lg border p-4">
|
||||
<p className="text-sm font-medium">테이블 데이터를 불러올 수 없습니다.</p>
|
||||
<p className="mt-1 text-xs text-gray-600">
|
||||
화면에 테이블이 연결되지 않았거나 테이블 목록이 로드되지 않았습니다.
|
||||
</p>
|
||||
</div>
|
||||
@@ -113,23 +160,12 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 테이블 정보 표시 */}
|
||||
<div className="rounded-lg bg-blue-50 p-3">
|
||||
<p className="text-xs text-blue-600">📊 사용 가능한 테이블: {tables.length}개</p>
|
||||
</div>
|
||||
|
||||
{/* 관계 타입 선택 (최상단) */}
|
||||
<div className="space-y-3 rounded-lg border-2 border-indigo-200 bg-indigo-50 p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-indigo-600 text-white">
|
||||
<span className="text-sm font-bold">1</span>
|
||||
</div>
|
||||
<h3 className="text-sm font-semibold text-indigo-900">패널 관계 타입 선택</h3>
|
||||
</div>
|
||||
<p className="text-xs text-indigo-700">좌측과 우측 패널 간의 데이터 관계를 선택하세요</p>
|
||||
{/* 관계 타입 선택 */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-sm font-semibold">패널 관계 타입</h3>
|
||||
<Select
|
||||
value={relationshipType}
|
||||
onValueChange={(value: "join" | "detail" | "custom") => {
|
||||
onValueChange={(value: "join" | "detail") => {
|
||||
// 상세 모드로 변경 시 우측 테이블을 현재 화면 테이블로 설정
|
||||
if (value === "detail" && screenTableName) {
|
||||
updateRightPanel({
|
||||
@@ -159,24 +195,13 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
|
||||
<span className="text-xs text-gray-500">좌측 테이블 → 우측 관련 테이블 (다른 테이블)</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="custom">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">커스텀 (CUSTOM)</span>
|
||||
<span className="text-xs text-gray-500">사용자 정의 관계</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 좌측 패널 설정 (마스터) */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-600 text-white">
|
||||
<span className="text-sm font-bold">2</span>
|
||||
</div>
|
||||
<h3 className="text-sm font-semibold text-gray-900">좌측 패널 설정 (마스터)</h3>
|
||||
</div>
|
||||
<h3 className="text-sm font-semibold">좌측 패널 설정 (마스터)</h3>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>패널 제목</Label>
|
||||
@@ -188,9 +213,11 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>테이블 (현재 화면)</Label>
|
||||
<Label>테이블 (현재 화면 고정)</Label>
|
||||
<div className="rounded-lg border border-gray-200 bg-gray-50 p-3">
|
||||
<p className="text-sm font-medium text-gray-900">{screenTableName || "테이블이 지정되지 않음"}</p>
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
{config.leftPanel?.tableName || screenTableName || "테이블이 지정되지 않음"}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-gray-500">좌측 패널은 현재 화면의 테이블 데이터를 표시합니다</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -214,14 +241,7 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
|
||||
|
||||
{/* 우측 패널 설정 */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-green-600 text-white">
|
||||
<span className="text-sm font-bold">3</span>
|
||||
</div>
|
||||
<h3 className="text-sm font-semibold text-gray-900">
|
||||
우측 패널 설정 ({relationshipType === "detail" ? "상세" : relationshipType === "join" ? "조인" : "커스텀"})
|
||||
</h3>
|
||||
</div>
|
||||
<h3 className="text-sm font-semibold">우측 패널 설정 ({relationshipType === "detail" ? "상세" : "조인"})</h3>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>패널 제목</Label>
|
||||
@@ -234,16 +254,18 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
|
||||
|
||||
{/* 관계 타입에 따라 테이블 선택 UI 변경 */}
|
||||
{relationshipType === "detail" ? (
|
||||
// 상세 모드: 좌측과 동일한 테이블 (비활성화)
|
||||
// 상세 모드: 좌측과 동일한 테이블 (자동 설정)
|
||||
<div className="space-y-2">
|
||||
<Label>테이블 (좌측과 동일)</Label>
|
||||
<div className="rounded-lg border border-gray-200 bg-gray-50 p-3">
|
||||
<p className="text-sm font-medium text-gray-900">{screenTableName || "테이블이 지정되지 않음"}</p>
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
{config.leftPanel?.tableName || screenTableName || "테이블이 지정되지 않음"}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-gray-500">상세 모드에서는 좌측과 동일한 테이블을 사용합니다</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
// 조인/커스텀 모드: 전체 테이블에서 선택 가능
|
||||
// 조인 모드: 전체 테이블에서 선택 가능
|
||||
<div className="space-y-2">
|
||||
<Label>테이블 선택 (전체 테이블)</Label>
|
||||
<Popover open={rightTableOpen} onOpenChange={setRightTableOpen}>
|
||||
@@ -289,7 +311,7 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 컬럼 매핑 - 조인/커스텀 모드에서만 표시 */}
|
||||
{/* 컬럼 매핑 - 조인 모드에서만 표시 */}
|
||||
{relationshipType !== "detail" && (
|
||||
<div className="space-y-3 rounded-lg border border-gray-200 bg-gray-50 p-3">
|
||||
<Label className="text-sm font-semibold">컬럼 매핑 (외래키 관계)</Label>
|
||||
@@ -418,12 +440,7 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
|
||||
|
||||
{/* 레이아웃 설정 */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-purple-600 text-white">
|
||||
<span className="text-sm font-bold">4</span>
|
||||
</div>
|
||||
<h3 className="text-sm font-semibold text-gray-900">레이아웃 설정</h3>
|
||||
</div>
|
||||
<h3 className="text-sm font-semibold">레이아웃 설정</h3>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>좌측 패널 너비: {config.splitRatio || 30}%</Label>
|
||||
|
||||
Reference in New Issue
Block a user