This commit is contained in:
kjs
2025-10-17 16:21:08 +09:00
parent 2a8081a253
commit 2e916678fa
22 changed files with 1641 additions and 871 deletions

View File

@@ -107,17 +107,17 @@ export function ComponentsPanel({ className }: ComponentsPanelProps) {
e.currentTarget.style.opacity = "1";
e.currentTarget.style.transform = "none";
}}
className="group cursor-grab rounded-lg border border-gray-200/40 bg-white/90 p-4 shadow-sm backdrop-blur-sm transition-all duration-300 hover:-translate-y-1 hover:scale-[1.02] hover:border-purple-300/60 hover:bg-white hover:shadow-lg hover:shadow-purple-500/15 active:translate-y-0 active:scale-[0.98] active:cursor-grabbing"
className="group bg-card hover:border-primary/50 cursor-grab rounded-lg border p-3 shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md active:translate-y-0 active:scale-[0.98] active:cursor-grabbing"
>
<div className="flex items-start space-x-3">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-purple-100 text-purple-700 shadow-md transition-all duration-300 group-hover:scale-110 group-hover:shadow-lg">
<div className="flex items-start gap-3">
<div className="bg-primary/10 text-primary group-hover:bg-primary/20 flex h-10 w-10 items-center justify-center rounded-md transition-all duration-200">
{getCategoryIcon(component.category)}
</div>
<div className="min-w-0 flex-1">
<h4 className="mb-1 text-sm leading-tight font-semibold text-gray-900">{component.name}</h4>
<p className="mb-2 line-clamp-2 text-xs leading-relaxed text-gray-500">{component.description}</p>
<div className="flex items-center space-x-2 text-xs text-gray-400">
<span className="rounded-full bg-purple-100 px-2 py-0.5 font-medium text-purple-700">
<h4 className="mb-1 text-xs leading-tight font-semibold">{component.name}</h4>
<p className="text-muted-foreground mb-1.5 line-clamp-2 text-xs leading-relaxed">{component.description}</p>
<div className="flex items-center">
<span className="bg-muted text-muted-foreground rounded-full px-2 py-0.5 text-xs font-medium">
{component.defaultSize.width}×{component.defaultSize.height}
</span>
</div>
@@ -128,80 +128,80 @@ export function ComponentsPanel({ className }: ComponentsPanelProps) {
// 빈 상태 렌더링
const renderEmptyState = () => (
<div className="flex h-32 items-center justify-center text-center text-gray-500">
<div className="p-8">
<Package className="mx-auto mb-3 h-12 w-12 text-gray-300" />
<p className="text-muted-foreground text-sm font-medium"> </p>
<p className="mt-1 text-xs text-gray-400"> </p>
<div className="flex h-32 items-center justify-center text-center">
<div className="p-6">
<Package 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>
);
return (
<div className={`flex h-full flex-col border-r border-gray-200/60 bg-slate-50 p-6 shadow-sm ${className}`}>
<div className={`bg-background flex h-full flex-col p-4 ${className}`}>
{/* 헤더 */}
<div className="mb-4">
<h2 className="mb-1 text-lg font-semibold text-gray-900"></h2>
<p className="text-sm text-gray-500">{allComponents.length} </p>
<div className="mb-3">
<h2 className="mb-0.5 text-sm font-semibold"></h2>
<p className="text-muted-foreground text-xs">{allComponents.length} </p>
</div>
{/* 검색 */}
<div className="mb-4">
<div className="mb-3">
<div className="relative">
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-gray-400" />
<Search className="text-muted-foreground absolute top-1/2 left-2.5 h-3.5 w-3.5 -translate-y-1/2" />
<Input
placeholder="컴포넌트 검색..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="border-0 bg-white/80 pl-10 shadow-sm backdrop-blur-sm transition-colors focus:bg-white"
className="h-8 pl-8 text-xs"
/>
</div>
</div>
{/* 카테고리 탭 */}
<Tabs defaultValue="input" className="flex flex-1 flex-col">
<TabsList className="mb-4 grid w-full grid-cols-4 bg-white/80 p-1">
<TabsTrigger value="input" className="flex items-center gap-1 text-xs">
<TabsList className="mb-3 grid h-8 w-full grid-cols-4">
<TabsTrigger value="input" className="flex items-center gap-1 px-1 text-xs">
<Edit3 className="h-3 w-3" />
<span className="hidden sm:inline"></span>
</TabsTrigger>
<TabsTrigger value="action" className="flex items-center gap-1 text-xs">
<TabsTrigger value="action" className="flex items-center gap-1 px-1 text-xs">
<Zap className="h-3 w-3" />
<span className="hidden sm:inline"></span>
</TabsTrigger>
<TabsTrigger value="display" className="flex items-center gap-1 text-xs">
<TabsTrigger value="display" className="flex items-center gap-1 px-1 text-xs">
<BarChart3 className="h-3 w-3" />
<span className="hidden sm:inline"></span>
</TabsTrigger>
<TabsTrigger value="layout" className="flex items-center gap-1 text-xs">
<TabsTrigger value="layout" className="flex items-center gap-1 px-1 text-xs">
<Layers className="h-3 w-3" />
<span className="hidden sm:inline"></span>
</TabsTrigger>
</TabsList>
{/* 입력 컴포넌트 */}
<TabsContent value="input" className="mt-0 flex-1 space-y-3 overflow-y-auto">
<TabsContent value="input" className="mt-0 flex-1 space-y-2 overflow-y-auto">
{getFilteredComponents("input").length > 0
? getFilteredComponents("input").map(renderComponentCard)
: renderEmptyState()}
</TabsContent>
{/* 액션 컴포넌트 */}
<TabsContent value="action" className="mt-0 flex-1 space-y-3 overflow-y-auto">
<TabsContent value="action" className="mt-0 flex-1 space-y-2 overflow-y-auto">
{getFilteredComponents("action").length > 0
? getFilteredComponents("action").map(renderComponentCard)
: renderEmptyState()}
</TabsContent>
{/* 표시 컴포넌트 */}
<TabsContent value="display" className="mt-0 flex-1 space-y-3 overflow-y-auto">
<TabsContent value="display" className="mt-0 flex-1 space-y-2 overflow-y-auto">
{getFilteredComponents("display").length > 0
? getFilteredComponents("display").map(renderComponentCard)
: renderEmptyState()}
</TabsContent>
{/* 레이아웃 컴포넌트 */}
<TabsContent value="layout" className="mt-0 flex-1 space-y-3 overflow-y-auto">
<TabsContent value="layout" className="mt-0 flex-1 space-y-2 overflow-y-auto">
{getFilteredComponents("layout").length > 0
? getFilteredComponents("layout").map(renderComponentCard)
: renderEmptyState()}
@@ -209,14 +209,12 @@ export function ComponentsPanel({ className }: ComponentsPanelProps) {
</Tabs>
{/* 도움말 */}
<div className="mt-4 rounded-xl border border-purple-100/60 bg-gradient-to-r from-purple-50 to-pink-50 p-4">
<div className="flex items-start space-x-3">
<MousePointer className="mt-0.5 h-4 w-4 flex-shrink-0 text-purple-600" />
<div className="flex-1">
<p className="text-xs leading-relaxed text-gray-700">
<span className="font-semibold text-purple-700"></span>
</p>
</div>
<div className="border-primary/20 bg-primary/5 mt-3 rounded-lg border p-3">
<div className="flex items-start gap-2">
<MousePointer className="text-primary mt-0.5 h-3.5 w-3.5 flex-shrink-0" />
<p className="text-muted-foreground text-xs leading-relaxed">
<span className="text-foreground font-semibold"></span>
</p>
</div>
</div>
</div>

View File

@@ -81,30 +81,30 @@ export const DetailSettingsPanel: React.FC<DetailSettingsPanelProps> = ({
return (
<div className="flex h-full flex-col">
{/* 헤더 */}
<div className="border-b border-gray-200 p-4">
<div className="flex items-center space-x-2">
<div className="border-b p-4">
<div className="flex items-center gap-2">
<Settings className="text-muted-foreground h-4 w-4" />
<h3 className="font-medium text-gray-900"> </h3>
<h3 className="text-sm font-semibold"> </h3>
</div>
<div className="mt-2 flex items-center space-x-2">
<span className="text-muted-foreground text-sm">:</span>
<span className="rounded bg-purple-100 px-2 py-1 text-xs font-medium text-purple-800">
<div className="mt-2 flex items-center gap-2">
<span className="text-muted-foreground text-xs">:</span>
<span className="bg-primary/10 text-primary rounded-md px-2 py-0.5 text-xs font-medium">
{layoutComponent.layoutType}
</span>
</div>
<div className="mt-1 text-xs text-gray-500">ID: {layoutComponent.id}</div>
<div className="text-muted-foreground mt-1 text-xs">ID: {layoutComponent.id}</div>
</div>
{/* 레이아웃 설정 영역 */}
<div className="flex-1 space-y-4 overflow-y-auto p-4">
<div className="flex-1 space-y-3 overflow-y-auto p-4">
{/* 기본 정보 */}
<div>
<label className="mb-2 block text-sm font-medium text-gray-700"> </label>
<div className="space-y-1.5">
<label className="text-xs font-medium"> </label>
<input
type="text"
value={layoutComponent.label || ""}
onChange={(e) => onUpdateProperty(layoutComponent.id, "label", e.target.value)}
className="focus:border-primary w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500"
className="border-input bg-background focus-visible:ring-ring h-8 w-full rounded-md border px-3 text-xs focus-visible:ring-1 focus-visible:outline-none"
placeholder="레이아웃 이름을 입력하세요"
/>
</div>

View File

@@ -55,44 +55,44 @@ export const GridPanel: React.FC<GridPanelProps> = ({
return (
<div className="flex h-full flex-col">
{/* 헤더 */}
<div className="border-b border-gray-200 p-4">
<div className="border-b p-4">
<div className="mb-3 flex items-center justify-between">
<div className="flex items-center space-x-2">
<Grid3X3 className="h-4 w-4 text-muted-foreground" />
<h3 className="font-medium text-gray-900"> </h3>
<div className="flex items-center gap-2">
<Grid3X3 className="text-muted-foreground h-4 w-4" />
<h3 className="text-sm font-semibold"> </h3>
</div>
<div className="flex items-center space-x-2">
<div className="flex items-center gap-1.5">
{onForceGridUpdate && (
<Button
size="sm"
variant="outline"
onClick={onForceGridUpdate}
className="flex items-center space-x-1"
className="h-7 px-2 text-xs"
title="현재 해상도에 맞게 모든 컴포넌트를 격자에 재정렬합니다"
>
<RefreshCw className="h-3 w-3" />
<span></span>
<RefreshCw className="mr-1 h-3 w-3" />
</Button>
)}
<Button size="sm" variant="outline" onClick={onResetGrid} className="flex items-center space-x-1">
<RotateCcw className="h-3 w-3" />
<span></span>
<Button size="sm" variant="outline" onClick={onResetGrid} className="h-7 px-2 text-xs">
<RotateCcw className="mr-1 h-3 w-3" />
</Button>
</div>
</div>
{/* 주요 토글들 */}
<div className="space-y-3">
<div className="space-y-2.5">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<div className="flex items-center gap-2">
{gridSettings.showGrid ? (
<Eye className="h-4 w-4 text-primary" />
<Eye className="text-primary h-3.5 w-3.5" />
) : (
<EyeOff className="h-4 w-4 text-gray-400" />
<EyeOff className="text-muted-foreground h-3.5 w-3.5" />
)}
<Label htmlFor="showGrid" className="text-sm font-medium">
<Label htmlFor="showGrid" className="text-xs font-medium">
</Label>
</div>
@@ -104,9 +104,9 @@ export const GridPanel: React.FC<GridPanelProps> = ({
</div>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Zap className="h-4 w-4 text-green-600" />
<Label htmlFor="snapToGrid" className="text-sm font-medium">
<div className="flex items-center gap-2">
<Zap className="text-primary h-3.5 w-3.5" />
<Label htmlFor="snapToGrid" className="text-xs font-medium">
</Label>
</div>
@@ -120,14 +120,14 @@ export const GridPanel: React.FC<GridPanelProps> = ({
</div>
{/* 설정 영역 */}
<div className="flex-1 space-y-6 overflow-y-auto p-4">
<div className="flex-1 space-y-4 overflow-y-auto p-4">
{/* 격자 구조 */}
<div className="space-y-4">
<h4 className="font-medium text-gray-900"> </h4>
<div className="space-y-3">
<h4 className="text-xs font-semibold"> </h4>
<div>
<Label htmlFor="columns" className="mb-2 block text-sm font-medium">
: {gridSettings.columns}
<div className="space-y-2">
<Label htmlFor="columns" className="text-xs font-medium">
: <span className="text-primary">{gridSettings.columns}</span>
</Label>
<Slider
id="columns"
@@ -138,15 +138,15 @@ export const GridPanel: React.FC<GridPanelProps> = ({
onValueChange={([value]) => updateSetting("columns", value)}
className="w-full"
/>
<div className="mt-1 flex justify-between text-xs text-gray-500">
<div className="text-muted-foreground flex justify-between text-xs">
<span>1</span>
<span>24</span>
</div>
</div>
<div>
<Label htmlFor="gap" className="mb-2 block text-sm font-medium">
: {gridSettings.gap}px
<div className="space-y-2">
<Label htmlFor="gap" className="text-xs font-medium">
: <span className="text-primary">{gridSettings.gap}px</span>
</Label>
<Slider
id="gap"
@@ -157,15 +157,15 @@ export const GridPanel: React.FC<GridPanelProps> = ({
onValueChange={([value]) => updateSetting("gap", value)}
className="w-full"
/>
<div className="mt-1 flex justify-between text-xs text-gray-500">
<div className="text-muted-foreground flex justify-between text-xs">
<span>0px</span>
<span>40px</span>
</div>
</div>
<div>
<Label htmlFor="padding" className="mb-2 block text-sm font-medium">
: {gridSettings.padding}px
<div className="space-y-2">
<Label htmlFor="padding" className="text-xs font-medium">
: <span className="text-primary">{gridSettings.padding}px</span>
</Label>
<Slider
id="padding"
@@ -176,7 +176,7 @@ export const GridPanel: React.FC<GridPanelProps> = ({
onValueChange={([value]) => updateSetting("padding", value)}
className="w-full"
/>
<div className="mt-1 flex justify-between text-xs text-gray-500">
<div className="text-muted-foreground flex justify-between text-xs">
<span>0px</span>
<span>60px</span>
</div>
@@ -248,8 +248,8 @@ export const GridPanel: React.FC<GridPanelProps> = ({
opacity: gridSettings.gridOpacity || 0.5,
}}
>
<div className="flex h-16 items-center justify-center rounded border-2 border-dashed border-blue-300 bg-primary/20">
<span className="text-xs text-primary"> </span>
<div className="bg-primary/20 flex h-16 items-center justify-center rounded border-2 border-dashed border-blue-300">
<span className="text-primary text-xs"> </span>
</div>
</div>
</div>
@@ -257,7 +257,7 @@ export const GridPanel: React.FC<GridPanelProps> = ({
{/* 푸터 */}
<div className="border-t border-gray-200 bg-gray-50 p-3">
<div className="text-xs text-muted-foreground">💡 </div>
<div className="text-muted-foreground text-xs">💡 </div>
{/* 해상도 및 격자 정보 */}
{screenResolution && actualGridInfo && (

View File

@@ -481,9 +481,13 @@ const PropertiesPanelComponent: React.FC<PropertiesPanelProps> = ({
if (!selectedComponent) {
return (
<div className="flex h-full flex-col items-center justify-center p-6 text-center">
<Settings className="mb-4 h-12 w-12 text-gray-400" />
<h3 className="mb-2 text-lg font-medium text-gray-900"> </h3>
<p className="text-sm text-gray-500"> .</p>
<Settings className="text-muted-foreground mb-3 h-10 w-10" />
<h3 className="mb-2 text-sm font-semibold"> </h3>
<p className="text-muted-foreground text-xs">
<br />
.
</p>
</div>
);
}
@@ -535,58 +539,58 @@ const PropertiesPanelComponent: React.FC<PropertiesPanelProps> = ({
<div className="flex h-full flex-col">
{/* 헤더 */}
<div className="border-b border-gray-200 p-4">
<div className="mb-3 flex items-center justify-between">
<div className="flex items-center space-x-2">
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<Settings className="text-muted-foreground h-4 w-4" />
<h3 className="font-medium text-gray-900"> </h3>
<h3 className="text-sm font-semibold"> </h3>
</div>
<Badge variant="secondary" className="text-xs">
<Badge variant="secondary" className="text-xs font-medium">
{selectedComponent.type}
</Badge>
</div>
{/* 액션 버튼들 */}
<div className="flex flex-wrap gap-2">
<Button size="sm" variant="outline" onClick={onCopyComponent} className="flex items-center space-x-1">
<Copy className="h-3 w-3" />
<span></span>
<div className="flex flex-wrap gap-1.5">
<Button size="sm" variant="outline" onClick={onCopyComponent} className="h-8 px-2.5 text-xs">
<Copy className="mr-1 h-3 w-3" />
</Button>
{canGroup && (
<Button size="sm" variant="outline" onClick={onGroupComponents} className="flex items-center space-x-1">
<Group className="h-3 w-3" />
<span></span>
<Button size="sm" variant="outline" onClick={onGroupComponents} className="h-8 px-2.5 text-xs">
<Group className="mr-1 h-3 w-3" />
</Button>
)}
{canUngroup && (
<Button size="sm" variant="outline" onClick={onUngroupComponents} className="flex items-center space-x-1">
<Ungroup className="h-3 w-3" />
<span></span>
<Button size="sm" variant="outline" onClick={onUngroupComponents} className="h-8 px-2.5 text-xs">
<Ungroup className="mr-1 h-3 w-3" />
</Button>
)}
<Button size="sm" variant="destructive" onClick={onDeleteComponent} className="flex items-center space-x-1">
<Trash2 className="h-3 w-3" />
<span></span>
<Button size="sm" variant="destructive" onClick={onDeleteComponent} className="h-8 px-2.5 text-xs">
<Trash2 className="mr-1 h-3 w-3" />
</Button>
</div>
</div>
{/* 속성 편집 영역 */}
<div className="flex-1 space-y-6 overflow-y-auto p-4">
<div className="flex-1 space-y-4 overflow-y-auto p-4">
{/* 기본 정보 */}
<div className="space-y-3">
<div className="flex items-center space-x-2">
<div className="flex items-center gap-2">
<Type className="text-muted-foreground h-4 w-4" />
<h4 className="font-medium text-gray-900"> </h4>
<h4 className="text-sm font-semibold"> </h4>
</div>
<div className="space-y-3">
{selectedComponent.type === "widget" && (
<>
<div>
<Label htmlFor="columnName" className="text-sm font-medium">
<div className="space-y-1.5">
<Label htmlFor="columnName" className="text-xs font-medium">
( )
</Label>
<Input
@@ -594,21 +598,20 @@ const PropertiesPanelComponent: React.FC<PropertiesPanelProps> = ({
value={selectedComponent.columnName || ""}
readOnly
placeholder="데이터베이스 컬럼명"
className="text-muted-foreground mt-1 bg-gray-50"
className="bg-muted/50 text-muted-foreground h-8"
title="컬럼명은 변경할 수 없습니다"
/>
</div>
<div>
<Label htmlFor="inputType" className="text-sm font-medium">
<div className="space-y-1.5">
<Label htmlFor="inputType" className="text-xs font-medium">
</Label>
<select
className="focus:border-primary mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:ring-1 focus:ring-blue-500 focus:outline-none"
className="border-input bg-background focus-visible:ring-ring flex h-8 w-full rounded-md border px-3 py-1 text-xs shadow-sm transition-colors focus-visible:ring-1 focus-visible:outline-none"
value={getBaseInputType(localInputs.widgetType)}
onChange={(e) => {
const selectedInputType = e.target.value as BaseInputType;
// 입력 타입에 맞는 기본 세부 타입 설정
const defaultWebType = getDefaultDetailType(selectedInputType);
setLocalInputs((prev) => ({ ...prev, widgetType: defaultWebType }));
onUpdateProperty("widgetType", defaultWebType);
@@ -620,11 +623,11 @@ const PropertiesPanelComponent: React.FC<PropertiesPanelProps> = ({
</option>
))}
</select>
<p className="mt-1 text-xs text-gray-500"> "상세 설정" </p>
<p className="text-muted-foreground text-xs"> "상세 설정" </p>
</div>
<div>
<Label htmlFor="placeholder" className="text-sm font-medium">
<div className="space-y-1.5">
<Label htmlFor="placeholder" className="text-xs font-medium">
</Label>
<Input
@@ -632,12 +635,11 @@ const PropertiesPanelComponent: React.FC<PropertiesPanelProps> = ({
value={localInputs.placeholder}
onChange={(e) => {
const newValue = e.target.value;
// console.log("🔄 placeholder 변경:", newValue);
setLocalInputs((prev) => ({ ...prev, placeholder: newValue }));
onUpdateProperty("placeholder", newValue);
}}
placeholder="입력 힌트 텍스트"
className="mt-1"
className="h-8"
/>
</div>

View File

@@ -34,7 +34,7 @@ const getWidgetIcon = (widgetType: WebType) => {
case "text":
case "email":
case "tel":
return <Type className="h-3 w-3 text-primary" />;
return <Type className="text-primary h-3 w-3" />;
case "number":
case "decimal":
return <Hash className="h-3 w-3 text-green-600" />;
@@ -49,9 +49,9 @@ const getWidgetIcon = (widgetType: WebType) => {
return <AlignLeft className="h-3 w-3 text-indigo-600" />;
case "boolean":
case "checkbox":
return <CheckSquare className="h-3 w-3 text-primary" />;
return <CheckSquare className="text-primary h-3 w-3" />;
case "code":
return <Code className="h-3 w-3 text-muted-foreground" />;
return <Code className="text-muted-foreground h-3 w-3" />;
case "entity":
return <Building className="h-3 w-3 text-cyan-600" />;
case "file":
@@ -89,55 +89,55 @@ export const TablesPanel: React.FC<TablesPanelProps> = ({
return (
<div className="flex h-full flex-col">
{/* 헤더 */}
<div className="border-b border-gray-200 p-4">
<div className="border-b p-4">
{selectedTableName && (
<div className="mb-3 rounded-md bg-accent p-3">
<div className="text-sm font-medium text-blue-900"> </div>
<div className="mt-1 flex items-center space-x-2">
<Database className="h-3 w-3 text-primary" />
<span className="font-mono text-xs text-blue-800">{selectedTableName}</span>
<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="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform text-gray-400" />
<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="테이블명, 컬럼명으로 검색..."
placeholder="테이블명, 컬럼명 검색..."
value={searchTerm}
onChange={(e) => onSearchChange(e.target.value)}
className="w-full rounded-md border border-gray-300 py-2 pr-3 pl-10 focus:border-transparent focus:ring-2 focus:ring-blue-500"
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="mt-2 text-xs text-muted-foreground"> {filteredTables.length} </div>
<div className="text-muted-foreground mt-2 text-xs"> {filteredTables.length}</div>
</div>
{/* 테이블 목록 */}
<div className="scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100 flex-1 overflow-y-auto">
<div className="space-y-1 p-2">
<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="rounded-md border border-gray-200">
<div key={table.tableName} className="bg-card rounded-lg border">
{/* 테이블 헤더 */}
<div
className="flex cursor-pointer items-center justify-between p-3 hover:bg-gray-50"
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 space-x-2">
<div className="flex flex-1 items-center gap-2">
{isExpanded ? (
<ChevronDown className="h-4 w-4 text-gray-500" />
<ChevronDown className="text-muted-foreground h-3.5 w-3.5" />
) : (
<ChevronRight className="h-4 w-4 text-gray-500" />
<ChevronRight className="text-muted-foreground h-3.5 w-3.5" />
)}
<Database className="h-4 w-4 text-primary" />
<div className="flex-1">
<div className="text-sm font-medium">{table.tableLabel || table.tableName}</div>
<div className="text-xs text-gray-500">{table.columns.length} </div>
<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>
@@ -146,7 +146,7 @@ export const TablesPanel: React.FC<TablesPanelProps> = ({
variant="ghost"
draggable
onDragStart={(e) => onDragStart(e, table)}
className="ml-2 text-xs"
className="h-6 px-2 text-xs"
>
</Button>
@@ -154,43 +154,33 @@ export const TablesPanel: React.FC<TablesPanelProps> = ({
{/* 컬럼 목록 */}
{isExpanded && (
<div className="border-t border-gray-200 bg-gray-50">
<div
className={`${
table.columns.length > 8
? "scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100 max-h-64 overflow-y-auto"
: ""
}`}
style={{
scrollbarWidth: "thin",
scrollbarColor: "#cbd5e1 #f1f5f9",
}}
>
<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={`flex cursor-pointer items-center justify-between p-2 hover:bg-white ${
index < table.columns.length - 1 ? "border-b border-gray-100" : ""
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 flex-1 items-center space-x-2">
<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-sm font-medium">
<div className="truncate text-xs font-semibold">
{column.columnLabel || column.columnName}
</div>
<div className="truncate text-xs text-gray-500">{column.dataType}</div>
<div className="text-muted-foreground truncate text-xs">{column.dataType}</div>
</div>
</div>
<div className="flex flex-shrink-0 items-center space-x-1">
<Badge variant="secondary" className="text-xs">
<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="text-xs">
<Badge variant="destructive" className="h-4 px-1.5 text-xs">
</Badge>
)}
@@ -200,8 +190,8 @@ export const TablesPanel: React.FC<TablesPanelProps> = ({
{/* 컬럼 수가 많을 때 안내 메시지 */}
{table.columns.length > 8 && (
<div className="sticky bottom-0 bg-gray-100 p-2 text-center">
<div className="text-xs text-muted-foreground">
<div className="bg-muted sticky bottom-0 p-2 text-center">
<div className="text-muted-foreground text-xs">
📜 {table.columns.length} ( )
</div>
</div>
@@ -217,7 +207,7 @@ export const TablesPanel: React.FC<TablesPanelProps> = ({
{/* 푸터 */}
<div className="border-t border-gray-200 bg-gray-50 p-3">
<div className="text-xs text-muted-foreground">💡 </div>
<div className="text-muted-foreground text-xs">💡 </div>
</div>
</div>
);