테이블 컬럼 검색 기능
This commit is contained in:
parent
711e051b1c
commit
b5605d93da
|
|
@ -162,14 +162,21 @@ export function ComponentsPanel({
|
|||
<p className="text-muted-foreground text-xs">{allComponents.length}개 사용 가능</p>
|
||||
</div>
|
||||
|
||||
{/* 검색 */}
|
||||
{/* 통합 검색 */}
|
||||
<div className="mb-3">
|
||||
<div className="relative">
|
||||
<Search className="text-muted-foreground absolute top-1/2 left-2.5 h-3.5 w-3.5 -translate-y-1/2" />
|
||||
<Input
|
||||
placeholder="컴포넌트 검색..."
|
||||
placeholder="컴포넌트, 테이블, 컬럼 검색..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setSearchQuery(value);
|
||||
// 테이블 검색도 함께 업데이트
|
||||
if (onSearchChange) {
|
||||
onSearchChange(value);
|
||||
}
|
||||
}}
|
||||
className="h-8 pl-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -177,26 +184,42 @@ export function ComponentsPanel({
|
|||
|
||||
{/* 카테고리 탭 */}
|
||||
<Tabs defaultValue="input" className="flex min-h-0 flex-1 flex-col">
|
||||
<TabsList className="mb-3 grid h-8 w-full flex-shrink-0 grid-cols-5">
|
||||
<TabsTrigger value="tables" className="flex items-center gap-1 px-1 text-xs">
|
||||
<TabsList className="mb-3 grid h-8 w-full flex-shrink-0 grid-cols-5 gap-1 p-1">
|
||||
<TabsTrigger
|
||||
value="tables"
|
||||
className="flex items-center justify-center gap-0.5 px-0 text-[10px]"
|
||||
title="테이블"
|
||||
>
|
||||
<Database className="h-3 w-3" />
|
||||
<span className="hidden sm:inline">테이블</span>
|
||||
<span className="hidden">테이블</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="input" className="flex items-center gap-1 px-1 text-xs">
|
||||
<TabsTrigger value="input" className="flex items-center justify-center gap-0.5 px-0 text-[10px]" title="입력">
|
||||
<Edit3 className="h-3 w-3" />
|
||||
<span className="hidden sm:inline">입력</span>
|
||||
<span className="hidden">입력</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="action" className="flex items-center gap-1 px-1 text-xs">
|
||||
<TabsTrigger
|
||||
value="action"
|
||||
className="flex items-center justify-center gap-0.5 px-0 text-[10px]"
|
||||
title="액션"
|
||||
>
|
||||
<Zap className="h-3 w-3" />
|
||||
<span className="hidden sm:inline">액션</span>
|
||||
<span className="hidden">액션</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="display" className="flex items-center gap-1 px-1 text-xs">
|
||||
<TabsTrigger
|
||||
value="display"
|
||||
className="flex items-center justify-center gap-0.5 px-0 text-[10px]"
|
||||
title="표시"
|
||||
>
|
||||
<BarChart3 className="h-3 w-3" />
|
||||
<span className="hidden sm:inline">표시</span>
|
||||
<span className="hidden">표시</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="layout" className="flex items-center gap-1 px-1 text-xs">
|
||||
<TabsTrigger
|
||||
value="layout"
|
||||
className="flex items-center justify-center gap-0.5 px-0 text-[10px]"
|
||||
title="레이아웃"
|
||||
>
|
||||
<Layers className="h-3 w-3" />
|
||||
<span className="hidden sm:inline">레이아웃</span>
|
||||
<span className="hidden">레이아웃</span>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import React from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Database,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Type,
|
||||
Hash,
|
||||
Calendar,
|
||||
CheckSquare,
|
||||
List,
|
||||
AlignLeft,
|
||||
Code,
|
||||
Building,
|
||||
File,
|
||||
Search,
|
||||
} from "lucide-react";
|
||||
import { Database, Type, Hash, Calendar, CheckSquare, List, AlignLeft, Code, Building, File } from "lucide-react";
|
||||
import { TableInfo, WebType } from "@/types/screen";
|
||||
|
||||
interface TablesPanelProps {
|
||||
|
|
@ -65,23 +50,9 @@ const getWidgetIcon = (widgetType: WebType) => {
|
|||
export const TablesPanel: React.FC<TablesPanelProps> = ({
|
||||
tables,
|
||||
searchTerm,
|
||||
onSearchChange,
|
||||
onDragStart,
|
||||
selectedTableName,
|
||||
placedColumns = new Set(),
|
||||
}) => {
|
||||
const [expandedTables, setExpandedTables] = useState<Set<string>>(new Set());
|
||||
|
||||
const toggleTable = (tableName: string) => {
|
||||
const newExpanded = new Set(expandedTables);
|
||||
if (newExpanded.has(tableName)) {
|
||||
newExpanded.delete(tableName);
|
||||
} else {
|
||||
newExpanded.add(tableName);
|
||||
}
|
||||
setExpandedTables(newExpanded);
|
||||
};
|
||||
|
||||
// 이미 배치된 컬럼을 제외한 테이블 정보 생성
|
||||
const tablesWithAvailableColumns = tables.map((table) => ({
|
||||
...table,
|
||||
|
|
@ -91,137 +62,89 @@ export const TablesPanel: React.FC<TablesPanelProps> = ({
|
|||
}),
|
||||
}));
|
||||
|
||||
// 검색어가 있으면 컬럼 필터링
|
||||
const filteredTables = tablesWithAvailableColumns
|
||||
.filter((table) => table.columns.length > 0) // 사용 가능한 컬럼이 있는 테이블만 표시
|
||||
.filter(
|
||||
(table) =>
|
||||
table.tableName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
table.columns.some((col) => col.columnName.toLowerCase().includes(searchTerm.toLowerCase())),
|
||||
);
|
||||
.map((table) => {
|
||||
if (!searchTerm) {
|
||||
return table;
|
||||
}
|
||||
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
|
||||
// 테이블명이 검색어와 일치하면 모든 컬럼 표시
|
||||
if (
|
||||
table.tableName.toLowerCase().includes(searchLower) ||
|
||||
(table.tableLabel && table.tableLabel.toLowerCase().includes(searchLower))
|
||||
) {
|
||||
return table;
|
||||
}
|
||||
|
||||
// 그렇지 않으면 컬럼명/라벨이 검색어와 일치하는 컬럼만 필터링
|
||||
const filteredColumns = table.columns.filter(
|
||||
(col) =>
|
||||
col.columnName.toLowerCase().includes(searchLower) ||
|
||||
(col.columnLabel && col.columnLabel.toLowerCase().includes(searchLower)),
|
||||
);
|
||||
|
||||
return {
|
||||
...table,
|
||||
columns: filteredColumns,
|
||||
};
|
||||
})
|
||||
.filter((table) => table.columns.length > 0); // 컬럼이 있는 테이블만 표시
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
{/* 헤더 */}
|
||||
<div className="border-b p-4">
|
||||
{selectedTableName && (
|
||||
<div className="border-primary/20 bg-primary/5 mb-3 rounded-lg border p-3">
|
||||
<div className="text-xs font-semibold">선택된 테이블</div>
|
||||
<div className="mt-1.5 flex items-center gap-2">
|
||||
<Database className="text-primary h-3 w-3" />
|
||||
<span className="font-mono text-xs font-medium">{selectedTableName}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 검색 */}
|
||||
<div className="relative">
|
||||
<Search className="text-muted-foreground absolute top-1/2 left-2.5 h-3.5 w-3.5 -translate-y-1/2" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="테이블명, 컬럼명 검색..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => onSearchChange(e.target.value)}
|
||||
className="border-input bg-background focus-visible:ring-ring h-8 w-full rounded-md border px-3 pl-8 text-xs focus-visible:ring-1 focus-visible:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="text-muted-foreground mt-2 text-xs">총 {filteredTables.length}개</div>
|
||||
</div>
|
||||
|
||||
{/* 테이블 목록 */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="space-y-1.5 p-3">
|
||||
{filteredTables.map((table) => {
|
||||
const isExpanded = expandedTables.has(table.tableName);
|
||||
|
||||
return (
|
||||
<div key={table.tableName} className="bg-card rounded-lg border">
|
||||
{/* 테이블 헤더 */}
|
||||
<div
|
||||
className="hover:bg-accent/50 flex cursor-pointer items-center justify-between p-2.5 transition-colors"
|
||||
onClick={() => toggleTable(table.tableName)}
|
||||
>
|
||||
<div className="flex flex-1 items-center gap-2">
|
||||
{isExpanded ? (
|
||||
<ChevronDown className="text-muted-foreground h-3.5 w-3.5" />
|
||||
) : (
|
||||
<ChevronRight className="text-muted-foreground h-3.5 w-3.5" />
|
||||
)}
|
||||
<Database className="text-primary h-3.5 w-3.5" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-xs font-semibold">{table.tableLabel || table.tableName}</div>
|
||||
<div className="text-muted-foreground text-xs">{table.columns.length}개</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
draggable
|
||||
onDragStart={(e) => onDragStart(e, table)}
|
||||
className="h-6 px-2 text-xs"
|
||||
>
|
||||
드래그
|
||||
</Button>
|
||||
{/* 테이블과 컬럼 평면 목록 */}
|
||||
<div className="flex-1 overflow-y-auto p-3">
|
||||
<div className="space-y-2">
|
||||
{filteredTables.map((table) => (
|
||||
<div key={table.tableName} className="space-y-1">
|
||||
{/* 테이블 헤더 */}
|
||||
<div className="bg-muted/50 flex items-center justify-between rounded-md p-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Database className="text-primary h-3.5 w-3.5" />
|
||||
<span className="text-xs font-semibold">{table.tableLabel || table.tableName}</span>
|
||||
<Badge variant="secondary" className="h-4 px-1.5 text-[10px]">
|
||||
{table.columns.length}개
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 컬럼 목록 */}
|
||||
{isExpanded && (
|
||||
<div className="bg-muted/30 border-t">
|
||||
<div className={`${table.columns.length > 8 ? "max-h-64 overflow-y-auto" : ""}`}>
|
||||
{table.columns.map((column, index) => (
|
||||
<div
|
||||
key={column.columnName}
|
||||
className={`hover:bg-accent/50 flex cursor-grab items-center justify-between p-2 transition-colors ${
|
||||
index < table.columns.length - 1 ? "border-border/50 border-b" : ""
|
||||
}`}
|
||||
draggable
|
||||
onDragStart={(e) => onDragStart(e, table, column)}
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2">
|
||||
{getWidgetIcon(column.widgetType)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-xs font-semibold">
|
||||
{column.columnLabel || column.columnName}
|
||||
</div>
|
||||
<div className="text-muted-foreground truncate text-xs">{column.dataType}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 컬럼 목록 (항상 표시) */}
|
||||
<div className="space-y-1 pl-2">
|
||||
{table.columns.map((column) => (
|
||||
<div
|
||||
key={column.columnName}
|
||||
className="hover:bg-accent/50 flex cursor-grab items-center justify-between rounded-md p-2 transition-colors"
|
||||
draggable
|
||||
onDragStart={(e) => onDragStart(e, table, column)}
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2">
|
||||
{getWidgetIcon(column.widgetType)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-xs font-medium">{column.columnLabel || column.columnName}</div>
|
||||
<div className="text-muted-foreground truncate text-[10px]">{column.dataType}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-shrink-0 items-center gap-1">
|
||||
<Badge variant="secondary" className="h-4 px-1.5 text-xs">
|
||||
{column.widgetType}
|
||||
</Badge>
|
||||
{column.required && (
|
||||
<Badge variant="destructive" className="h-4 px-1.5 text-xs">
|
||||
필수
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* 컬럼 수가 많을 때 안내 메시지 */}
|
||||
{table.columns.length > 8 && (
|
||||
<div className="bg-muted sticky bottom-0 p-2 text-center">
|
||||
<div className="text-muted-foreground text-xs">
|
||||
📜 총 {table.columns.length}개 컬럼 (스크롤하여 더 보기)
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 items-center gap-1">
|
||||
<Badge variant="secondary" className="h-4 px-1.5 text-[10px]">
|
||||
{column.widgetType}
|
||||
</Badge>
|
||||
{column.required && (
|
||||
<Badge variant="destructive" className="h-4 px-1 text-[10px]">
|
||||
필수
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 푸터 */}
|
||||
<div className="border-t border-gray-200 bg-gray-50 p-3">
|
||||
<div className="text-muted-foreground text-xs">💡 테이블이나 컬럼을 캔버스로 드래그하세요</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue