ERP-node/frontend/lib/meta-components/config/UnifiedConfigPanel.tsx

1672 lines
69 KiB
TypeScript

"use client";
/**
* 통합 설정 패널 (UnifiedConfigPanel) - Phase C: 스마트 Config
* - 테이블 선택 → 컬럼 자동 로드 → webType 자동 감지
* - 메타 컴포넌트 타입별 심플/고급 모드
* - API 클라이언트 사용 (fetch 금지)
*/
import React, { useState, useEffect } from "react";
import { cn } from "@/lib/utils";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { Badge } from "@/components/ui/badge";
import { Checkbox } from "@/components/ui/checkbox";
import { Settings2, Eye, EyeOff, Plus, Trash2, RefreshCw, Database, Palette, Link2, Settings, Wand2, SlidersHorizontal } from "lucide-react";
import { MetaComponent, getFieldConfig } from "@/lib/api/metaComponent";
import { apiClient } from "@/lib/api/client";
import { toast } from "sonner";
interface UnifiedConfigPanelProps {
component: MetaComponent | null;
onChange: (config: any) => void;
className?: string;
}
// 테이블 목록, 컬럼 목록 타입
interface TableInfo {
tableName: string;
displayName?: string;
description?: string;
}
interface ColumnInfo {
columnName: string;
dataType: string;
comment?: string;
}
// DB 타입 → webType 자동 매핑
function inferWebType(dbType: string): string {
const type = dbType.toLowerCase();
if (type.includes("varchar") || type.includes("text") || type.includes("char")) return "text";
if (type.includes("int") || type.includes("serial") || type.includes("bigint")) return "number";
if (type.includes("numeric") || type.includes("decimal") || type.includes("float") || type.includes("double")) return "number";
if (type.includes("bool")) return "checkbox";
if (type === "date") return "date";
if (type.includes("timestamp")) return "datetime";
if (type.includes("json")) return "textarea";
return "text";
}
export function UnifiedConfigPanel({ component, onChange, className }: UnifiedConfigPanelProps) {
const [isAdvanced, setIsAdvanced] = useState(false);
// Phase C: 테이블/컬럼 자동 로드 상태
const [tables, setTables] = useState<TableInfo[]>([]);
const [columns, setColumns] = useState<ColumnInfo[]>([]);
const [loadingTables, setLoadingTables] = useState(false);
const [loadingColumns, setLoadingColumns] = useState(false);
// Phase C: 컴포넌트 마운트 시 테이블 목록 로드
useEffect(() => {
loadTables();
}, []);
// Phase C: 테이블 선택 시 컬럼 로드
useEffect(() => {
if (component?.config?.tableName) {
loadColumns(component.config.tableName);
}
}, [component?.config?.tableName]);
// 테이블 목록 가져오기
const loadTables = async () => {
setLoadingTables(true);
try {
const response = await apiClient.get("/table-management/tables");
const data = response.data?.data || [];
setTables(data);
} catch (error: any) {
console.error("테이블 목록 로드 실패:", error);
toast.error("테이블 목록을 불러올 수 없습니다");
} finally {
setLoadingTables(false);
}
};
// 특정 테이블의 컬럼 목록 가져오기
const loadColumns = async (tableName: string) => {
if (!tableName) return;
setLoadingColumns(true);
try {
const response = await apiClient.get(`/table-management/tables/${tableName}/columns?size=1000`);
const data = response.data?.data || response.data;
const cols = data.columns || data || [];
setColumns(cols);
} catch (error: any) {
console.error("컬럼 목록 로드 실패:", error);
toast.error("컬럼 목록을 불러올 수 없습니다");
setColumns([]);
} finally {
setLoadingColumns(false);
}
};
if (!component) {
return (
<div className={cn("flex h-full items-center justify-center bg-muted/10", className)}>
<div className="text-center">
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-primary/10">
<Settings2 className="h-8 w-8 text-primary" />
</div>
<h3 className="mb-2 text-base font-semibold text-foreground"> </h3>
<p className="text-muted-foreground text-xs"> </p>
</div>
</div>
);
}
const { type, config } = component;
// 설정 변경 핸들러
const handleConfigChange = (key: string, value: any) => {
onChange({ ...config, [key]: value });
};
// 중첩 설정 변경 핸들러 (예: text.content)
const handleNestedChange = (path: string[], value: any) => {
const newConfig = { ...config };
let current: any = newConfig;
for (let i = 0; i < path.length - 1; i++) {
if (!current[path[i]]) current[path[i]] = {};
current = current[path[i]];
}
current[path[path.length - 1]] = value;
onChange(newConfig);
};
// 공통 기본 설정 (모든 컴포넌트)
const renderBasicTab = () => (
<TabsContent value="basic" className="space-y-4">
{/* 컴포넌트 유형 섹션 */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Badge variant="outline" className="text-xs">{type}</Badge>
</div>
<Separator />
</div>
{/* Field 컴포넌트 설정 - Phase C: 스마트 설정 */}
{type === "meta-field" && (
<>
{/* 심플 모드: 테이블 → 컬럼 → 라벨 → webType */}
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> *</Label>
<Select
value={(config as any).tableName || ""}
onValueChange={(tableName) => {
handleConfigChange("tableName", tableName);
// 테이블 선택하면 컬럼 초기화
handleConfigChange("binding", "");
handleConfigChange("label", "");
}}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder={loadingTables ? "로딩 중..." : "테이블 선택"} />
</SelectTrigger>
<SelectContent>
{tables.map((table) => (
<SelectItem key={table.tableName} value={table.tableName}>
{table.displayName || table.tableName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{(config as any).tableName && (
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> *</Label>
<Select
value={(config as any).binding || ""}
onValueChange={(columnName) => {
handleConfigChange("binding", columnName);
// 컬럼 선택 시 자동으로 라벨과 webType 설정
const selectedCol = columns.find((c) => c.columnName === columnName);
if (selectedCol) {
const label = selectedCol.comment || columnName;
const webType = inferWebType(selectedCol.dataType);
handleConfigChange("label", label);
handleConfigChange("webType", webType);
toast.success(`라벨: ${label}, 타입: ${webType}`);
}
}}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder={loadingColumns ? "로딩 중..." : "컬럼 선택"} />
</SelectTrigger>
<SelectContent>
{columns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.comment ? `${col.comment} (${col.columnName})` : col.columnName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Input
value={(config as any).label || ""}
onChange={(e) => handleConfigChange("label", e.target.value)}
placeholder="예: 고객명, 주문번호..."
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Select value={(config as any).webType || "text"} onValueChange={(v) => handleConfigChange("webType", v)}>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder="필드 타입 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="text"></SelectItem>
<SelectItem value="number"></SelectItem>
<SelectItem value="date"></SelectItem>
<SelectItem value="datetime"></SelectItem>
<SelectItem value="checkbox"></SelectItem>
<SelectItem value="textarea"> </SelectItem>
<SelectItem value="select"></SelectItem>
<SelectItem value="entity"></SelectItem>
</SelectContent>
</Select>
</div>
{isAdvanced && (
<>
<Separator className="my-4" />
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Input
value={(config as any).placeholder || ""}
onChange={(e) => handleConfigChange("placeholder", e.target.value)}
placeholder="입력 필드에 표시될 힌트 텍스트"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<div className="flex items-center justify-between rounded-lg border p-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Switch
checked={(config as any).required || false}
onCheckedChange={(checked) => handleConfigChange("required", checked)}
/>
</div>
<div className="flex items-center justify-between rounded-lg border p-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Switch
checked={(config as any).readonly || false}
onCheckedChange={(checked) => handleConfigChange("readonly", checked)}
/>
</div>
</>
)}
</>
)}
{/* DataView 컴포넌트 설정 - Phase C */}
{type === "meta-dataview" && (
<>
{/* 심플 모드: 테이블 → 페이지 크기 */}
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> *</Label>
<Select
value={(config as any).tableName || ""}
onValueChange={(tableName) => {
handleConfigChange("tableName", tableName);
handleConfigChange("columns", ""); // 컬럼 선택 초기화
}}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder={loadingTables ? "로딩 중..." : "테이블 선택"} />
</SelectTrigger>
<SelectContent>
{tables.map((table) => (
<SelectItem key={table.tableName} value={table.tableName}>
{table.displayName || table.tableName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Select value={(config as any).viewMode || "table"} onValueChange={(v) => handleConfigChange("viewMode", v)}>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder="뷰 모드 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="table"></SelectItem>
<SelectItem value="card"></SelectItem>
<SelectItem value="list"></SelectItem>
<SelectItem value="tree"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Input
type="number"
value={(config as any).pageSize || 10}
onChange={(e) => handleConfigChange("pageSize", parseInt(e.target.value) || 10)}
placeholder="10"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
{isAdvanced && (config as any).tableName && (
<>
<Separator className="my-4" />
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<p className="text-[10px] text-muted-foreground"> </p>
<div className="max-h-[200px] space-y-2 overflow-y-auto rounded border p-3">
{loadingColumns ? (
<p className="text-xs text-muted-foreground"> ...</p>
) : columns.length === 0 ? (
<p className="text-xs text-muted-foreground"> </p>
) : (
columns.map((col) => {
const selectedColumns = ((config as any).columns || "").split(",").filter(Boolean);
const isChecked = selectedColumns.includes(col.columnName);
return (
<div key={col.columnName} className="flex items-center gap-2">
<Checkbox
checked={isChecked}
onCheckedChange={(checked) => {
let updatedColumns = [...selectedColumns];
if (checked) {
updatedColumns.push(col.columnName);
} else {
updatedColumns = updatedColumns.filter((c) => c !== col.columnName);
}
handleConfigChange("columns", updatedColumns.join(","));
}}
/>
<Label className="text-xs">
{col.comment ? `${col.comment} (${col.columnName})` : col.columnName}
</Label>
</div>
);
})
)}
</div>
</div>
</>
)}
</>
)}
{/* Action 컴포넌트 설정 - Phase C */}
{type === "meta-action" && (
<>
{/* 심플 모드: 액션 유형 → 대상 테이블 → 버튼 텍스트 → 버튼 스타일 */}
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Select
value={(config as any).actionType || "save"}
onValueChange={(v) => handleConfigChange("actionType", v)}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder="액션 유형 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="save"></SelectItem>
<SelectItem value="delete"></SelectItem>
<SelectItem value="refresh"></SelectItem>
<SelectItem value="custom"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> ()</Label>
<Select
value={(config as any).targetTable || ""}
onValueChange={(v) => handleConfigChange("targetTable", v)}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder={loadingTables ? "로딩 중..." : "테이블 선택"} />
</SelectTrigger>
<SelectContent>
<SelectItem value=""> </SelectItem>
{tables.map((table) => (
<SelectItem key={table.tableName} value={table.tableName}>
{table.displayName || table.tableName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Input
value={(config as any).label || ""}
onChange={(e) => handleConfigChange("label", e.target.value)}
placeholder="예: 저장, 삭제, 조회..."
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Select
value={(config as any).buttonType || "primary"}
onValueChange={(v) => handleConfigChange("buttonType", v)}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder="버튼 스타일 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="primary">Primary</SelectItem>
<SelectItem value="secondary">Secondary</SelectItem>
<SelectItem value="destructive">Destructive</SelectItem>
<SelectItem value="ghost">Ghost</SelectItem>
<SelectItem value="outline">Outline</SelectItem>
</SelectContent>
</Select>
</div>
{isAdvanced && (
<>
<Separator className="my-4" />
<div className="flex items-center justify-between rounded-lg border p-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Switch
checked={(config as any).confirmDialog || false}
onCheckedChange={(checked) => handleConfigChange("confirmDialog", checked)}
/>
</div>
</>
)}
</>
)}
{/* Layout 컴포넌트 설정 */}
{type === "meta-layout" && (
<>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Select value={(config as any).mode} onValueChange={(v) => handleConfigChange("mode", v)}>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder="레이아웃 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="columns"></SelectItem>
<SelectItem value="rows"></SelectItem>
<SelectItem value="tabs"></SelectItem>
<SelectItem value="accordion"></SelectItem>
<SelectItem value="card"></SelectItem>
</SelectContent>
</Select>
</div>
{isAdvanced && (
<>
<Separator className="my-4" />
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> (gap)</Label>
<Input
type="number"
value={(config as any).gap || 4}
onChange={(e) => handleConfigChange("gap", parseInt(e.target.value))}
placeholder="4"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> (padding)</Label>
<Input
type="number"
value={(config as any).padding || 4}
onChange={(e) => handleConfigChange("padding", parseInt(e.target.value))}
placeholder="4"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<div className="flex items-center justify-between rounded-lg border p-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Switch
checked={(config as any).bordered || false}
onCheckedChange={(checked) => handleConfigChange("bordered", checked)}
/>
</div>
</>
)}
</>
)}
{/* Display 컴포넌트 설정 */}
{type === "meta-display" && (
<>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Select value={(config as any).displayType} onValueChange={(v) => handleConfigChange("displayType", v)}>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder="표시 타입 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="text"></SelectItem>
<SelectItem value="heading"></SelectItem>
<SelectItem value="divider"></SelectItem>
<SelectItem value="badge"></SelectItem>
<SelectItem value="alert"></SelectItem>
<SelectItem value="stat"></SelectItem>
<SelectItem value="spacer"></SelectItem>
<SelectItem value="progress"></SelectItem>
</SelectContent>
</Select>
</div>
{(config as any).displayType === "text" && (
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Input
value={(config as any).text?.content || ""}
onChange={(e) => handleNestedChange(["text", "content"], e.target.value)}
placeholder="표시할 텍스트를 입력하세요"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
)}
{(config as any).displayType === "heading" && (
<>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Input
value={(config as any).heading?.content || ""}
onChange={(e) => handleNestedChange(["heading", "content"], e.target.value)}
placeholder="제목 텍스트를 입력하세요"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Select
value={String((config as any).heading?.level || 2)}
onValueChange={(v) => handleNestedChange(["heading", "level"], parseInt(v))}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
{[1, 2, 3, 4, 5, 6].map((level) => (
<SelectItem key={level} value={String(level)}>
H{level}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</>
)}
{(config as any).displayType === "stat" && (
<>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Input
value={(config as any).stat?.value || ""}
onChange={(e) => handleNestedChange(["stat", "value"], e.target.value)}
placeholder="예: 1,234"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Input
value={(config as any).stat?.label || ""}
onChange={(e) => handleNestedChange(["stat", "label"], e.target.value)}
placeholder="예: 총 판매액"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
</>
)}
</>
)}
{/* Search 컴포넌트 설정 - Phase C */}
{type === "meta-search" && (
<>
{/* 심플 모드: 대상 DataView → 검색 필드 추가 */}
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Select value={(config as any).mode || "simple"} onValueChange={(v) => handleConfigChange("mode", v)}>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder="검색 모드 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="simple">Simple</SelectItem>
<SelectItem value="advanced">Advanced</SelectItem>
<SelectItem value="combined">Combined</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> DataView ID</Label>
<Input
value={(config as any).targetDataView || ""}
onChange={(e) => handleConfigChange("targetDataView", e.target.value)}
placeholder="예: dataview-1"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
<p className="text-[10px] text-muted-foreground">
DataView ID를
</p>
</div>
<Separator className="my-4" />
<div className="flex items-center justify-between">
<div>
<Label className="text-xs font-medium"> </Label>
<p className="mt-1 text-[10px] text-muted-foreground"> </p>
</div>
<Button
variant="outline"
size="sm"
onClick={() => {
const fields = (config as any).searchFields || [];
handleConfigChange("searchFields", [
...fields,
{ column: "", label: "", searchType: "contains" },
]);
}}
>
<Plus className="mr-1 h-4 w-4" />
</Button>
</div>
{((config as any).searchFields || []).map((field: any, idx: number) => (
<div key={idx} className="space-y-2 rounded border p-3">
<div className="flex items-center justify-between">
<span className="text-xs font-medium"> {idx + 1}</span>
<Button
variant="ghost"
size="sm"
onClick={() => {
const fields = [...(config as any).searchFields];
fields.splice(idx, 1);
handleConfigChange("searchFields", fields);
}}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
<Input
value={field.column || ""}
onChange={(e) => {
const fields = [...(config as any).searchFields];
fields[idx].column = e.target.value;
handleConfigChange("searchFields", fields);
}}
placeholder="컬럼명 (예: customer_name)"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
<Input
value={field.label || ""}
onChange={(e) => {
const fields = [...(config as any).searchFields];
fields[idx].label = e.target.value;
handleConfigChange("searchFields", fields);
}}
placeholder="라벨 (예: 고객명)"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
{isAdvanced && (
<Select
value={field.searchType || "contains"}
onValueChange={(v) => {
const fields = [...(config as any).searchFields];
fields[idx].searchType = v;
handleConfigChange("searchFields", fields);
}}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="contains"></SelectItem>
<SelectItem value="equals"></SelectItem>
<SelectItem value="startsWith"></SelectItem>
<SelectItem value="endsWith"></SelectItem>
</SelectContent>
</Select>
)}
</div>
))}
</>
)}
{/* Modal 컴포넌트 설정 */}
{type === "meta-modal" && (
<>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Select value={(config as any).trigger} onValueChange={(v) => handleConfigChange("trigger", v)}>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder="모달 열림 방식 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="button"></SelectItem>
<SelectItem value="row_click"> </SelectItem>
<SelectItem value="row_double_click"> </SelectItem>
<SelectItem value="action"></SelectItem>
</SelectContent>
</Select>
</div>
{(config as any).trigger === "button" && (
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Input
value={(config as any).triggerLabel || ""}
onChange={(e) => handleConfigChange("triggerLabel", e.target.value)}
placeholder="예: 상세보기, 추가..."
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
)}
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Select value={(config as any).size || "md"} onValueChange={(v) => handleConfigChange("size", v)}>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="sm">Small</SelectItem>
<SelectItem value="md">Medium</SelectItem>
<SelectItem value="lg">Large</SelectItem>
<SelectItem value="xl">Extra Large</SelectItem>
<SelectItem value="full">Full</SelectItem>
</SelectContent>
</Select>
</div>
</>
)}
</TabsContent>
);
// 데이터 탭
const renderDataTab = () => {
// DB 설정 가져오기 핸들러 (기존 유지)
const handleFetchFieldConfig = async () => {
const tableName = (config as any).tableName;
const columnName = (config as any).binding;
if (!tableName || !columnName) {
toast.error("테이블명과 컬럼명을 먼저 입력하세요");
return;
}
try {
const response = await getFieldConfig(tableName, columnName);
if (response.success && response.data) {
// DB 설정으로 config 자동 채우기
const dbConfig = response.data;
onChange({
...config,
webType: dbConfig.webType || (config as any).webType,
label: dbConfig.label || (config as any).label,
required: dbConfig.required !== undefined ? dbConfig.required : (config as any).required,
options: dbConfig.options || (config as any).options,
join: dbConfig.join || (config as any).join,
});
toast.success("DB 설정을 가져왔습니다");
} else {
toast.error(response.error || "설정을 가져올 수 없습니다");
}
} catch (error: any) {
toast.error("DB 설정 가져오기 실패: " + error.message);
}
};
return (
<TabsContent value="data" className="space-y-4">
{/* meta-field */}
{type === "meta-field" && (
<>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Input
value={(config as any).defaultValue || ""}
onChange={(e) => handleConfigChange("defaultValue", e.target.value)}
placeholder="필드 초기값"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<Separator className="my-4" />
<Button
variant="outline"
size="sm"
onClick={handleFetchFieldConfig}
className="w-full"
>
<RefreshCw className="mr-2 h-4 w-4" />
DB에서
</Button>
</>
)}
{/* meta-dataview - Phase C: 데이터 탭에서는 정렬/페이징만 */}
{type === "meta-dataview" && (
<>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Input
value={(config as any).defaultSortColumn || ""}
onChange={(e) => handleConfigChange("defaultSortColumn", e.target.value)}
placeholder="예: created_at"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Select
value={(config as any).defaultSortDirection || "desc"}
onValueChange={(v) => handleConfigChange("defaultSortDirection", v)}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="asc"></SelectItem>
<SelectItem value="desc"></SelectItem>
</SelectContent>
</Select>
</div>
</>
)}
{/* meta-modal (form 타입) - Phase C: 심플하게 */}
{type === "meta-modal" && (
<>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Select
value={(config as any).tableName || ""}
onValueChange={(v) => handleConfigChange("tableName", v)}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder={loadingTables ? "로딩 중..." : "테이블 선택"} />
</SelectTrigger>
<SelectContent>
{tables.map((table) => (
<SelectItem key={table.tableName} value={table.tableName}>
{table.displayName || table.tableName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Select
value={(config as any).mode || "create"}
onValueChange={(v) => handleConfigChange("mode", v)}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="create"></SelectItem>
<SelectItem value="edit"></SelectItem>
<SelectItem value="view"></SelectItem>
</SelectContent>
</Select>
</div>
{isAdvanced && (
<>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Select
value={(config as any).formLayout || "single"}
onValueChange={(v) => handleConfigChange("formLayout", v)}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="single"> </SelectItem>
<SelectItem value="two_column">2 </SelectItem>
</SelectContent>
</Select>
</div>
<Separator className="my-4" />
<div className="flex items-center justify-between">
<div>
<Label className="text-xs font-medium"> </Label>
<p className="mt-1 text-[10px] text-muted-foreground"> </p>
</div>
<Button
variant="outline"
size="sm"
onClick={() => {
const cols = (config as any).formColumns || [];
handleConfigChange("formColumns", [
...cols,
{ column: "", label: "", type: "text" },
]);
}}
>
<Plus className="mr-1 h-4 w-4" />
</Button>
</div>
{((config as any).formColumns || []).map((col: any, idx: number) => (
<div key={idx} className="space-y-2 rounded border p-3">
<div className="flex items-center justify-between">
<span className="text-xs font-medium"> {idx + 1}</span>
<Button
variant="ghost"
size="sm"
onClick={() => {
const cols = [...(config as any).formColumns];
cols.splice(idx, 1);
handleConfigChange("formColumns", cols);
}}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
<Input
value={col.column || ""}
onChange={(e) => {
const cols = [...(config as any).formColumns];
cols[idx].column = e.target.value;
handleConfigChange("formColumns", cols);
}}
placeholder="컬럼명"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
<Input
value={col.label || ""}
onChange={(e) => {
const cols = [...(config as any).formColumns];
cols[idx].label = e.target.value;
handleConfigChange("formColumns", cols);
}}
placeholder="라벨"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
<Select
value={col.type || "text"}
onValueChange={(v) => {
const cols = [...(config as any).formColumns];
cols[idx].type = v;
handleConfigChange("formColumns", cols);
}}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="text"></SelectItem>
<SelectItem value="number"></SelectItem>
<SelectItem value="date"></SelectItem>
<SelectItem value="select"></SelectItem>
</SelectContent>
</Select>
</div>
))}
</>
)}
</>
)}
{/* 기타 컴포넌트 - 데이터 설정 불필요 */}
{!["meta-field", "meta-dataview", "meta-modal"].includes(type) && (
<div className="flex flex-col items-center justify-center py-12 text-center">
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-muted">
<Database className="h-8 w-8 text-muted-foreground" />
</div>
<h4 className="mb-2 text-sm font-medium"> </h4>
<p className="max-w-[250px] text-xs text-muted-foreground">
.
</p>
</div>
)}
</TabsContent>
);
};
// 표시 탭
const renderDisplayTab = () => (
<TabsContent value="display" className="space-y-4">
{/* 공통 크기 설정 섹션 */}
<div className="space-y-3">
<div className="flex items-center gap-2">
<Label className="text-xs font-medium"> </Label>
</div>
<Separator className="mb-4" />
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Input
value={(config as any).width || ""}
onChange={(e) => handleConfigChange("width", e.target.value)}
placeholder="예: 100%, 400px, auto"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Input
value={(config as any).height || ""}
onChange={(e) => handleConfigChange("height", e.target.value)}
placeholder="예: 200px, auto"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
{isAdvanced && (
<>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Input
value={(config as any).minWidth || ""}
onChange={(e) => handleConfigChange("minWidth", e.target.value)}
placeholder="예: 200px"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Input
value={(config as any).maxWidth || ""}
onChange={(e) => handleConfigChange("maxWidth", e.target.value)}
placeholder="예: 1200px"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
</>
)}
</div>
<Separator className="my-6" />
{/* meta-field 전용 */}
{type === "meta-field" && (
<>
<div className="space-y-3">
<div className="flex items-center gap-2">
<Label className="text-xs font-medium"> </Label>
</div>
<Separator className="mb-4" />
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Select
value={(config as any).labelPosition || "top"}
onValueChange={(v) => handleConfigChange("labelPosition", v)}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="top"></SelectItem>
<SelectItem value="left"></SelectItem>
<SelectItem value="hidden"></SelectItem>
</SelectContent>
</Select>
</div>
{(config as any).labelPosition === "left" && (
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Input
value={(config as any).labelWidth || "120px"}
onChange={(e) => handleConfigChange("labelWidth", e.target.value)}
placeholder="예: 120px"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
)}
</div>
</>
)}
{/* meta-display 전용 */}
{type === "meta-display" && (
<>
<div className="space-y-3">
<div className="flex items-center gap-2">
<Label className="text-xs font-medium"> </Label>
</div>
<Separator className="mb-4" />
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Select
value={(config as any).textSize || "md"}
onValueChange={(v) => handleConfigChange("textSize", v)}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="xs">XS</SelectItem>
<SelectItem value="sm">SM</SelectItem>
<SelectItem value="md">MD</SelectItem>
<SelectItem value="lg">LG</SelectItem>
<SelectItem value="xl">XL</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Select
value={(config as any).fontWeight || "normal"}
onValueChange={(v) => handleConfigChange("fontWeight", v)}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="normal">Normal</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="semibold">Semibold</SelectItem>
<SelectItem value="bold">Bold</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Select
value={(config as any).textAlign || "left"}
onValueChange={(v) => handleConfigChange("textAlign", v)}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="left"></SelectItem>
<SelectItem value="center"></SelectItem>
<SelectItem value="right"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
</>
)}
{/* meta-layout 전용 */}
{type === "meta-layout" && isAdvanced && (
<>
<div className="space-y-3">
<div className="flex items-center gap-2">
<Label className="text-xs font-medium"> </Label>
</div>
<Separator className="mb-4" />
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> (gap)</Label>
<Input
type="number"
value={(config as any).gap || 4}
onChange={(e) => handleConfigChange("gap", parseInt(e.target.value))}
placeholder="4"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> (padding)</Label>
<Input
type="number"
value={(config as any).padding || 4}
onChange={(e) => handleConfigChange("padding", parseInt(e.target.value))}
placeholder="4"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<div className="flex items-center justify-between rounded-lg border p-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Switch
checked={(config as any).bordered || false}
onCheckedChange={(checked) => handleConfigChange("bordered", checked)}
/>
</div>
</div>
</>
)}
{/* meta-action 전용 */}
{type === "meta-action" && (
<>
<div className="space-y-3">
<div className="flex items-center gap-2">
<Label className="text-xs font-medium"> </Label>
</div>
<Separator className="mb-4" />
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Select
value={(config as any).buttonSize || "default"}
onValueChange={(v) => handleConfigChange("buttonSize", v)}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="sm">Small</SelectItem>
<SelectItem value="default">Default</SelectItem>
<SelectItem value="lg">Large</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> (lucide )</Label>
<Input
value={(config as any).icon || ""}
onChange={(e) => handleConfigChange("icon", e.target.value)}
placeholder="예: Save, Edit, Trash2"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
</div>
</>
)}
</TabsContent>
);
// 연동 탭
const renderBindingTab = () => {
const bindings = (config as any).bindings || [];
const addBinding = () => {
handleConfigChange("bindings", [
...bindings,
{
sourceComponentId: "",
sourceEvent: "change",
sourceField: "",
targetComponentId: "",
targetAction: "filter",
targetField: "",
},
]);
};
const removeBinding = (idx: number) => {
const newBindings = [...bindings];
newBindings.splice(idx, 1);
handleConfigChange("bindings", newBindings);
};
const updateBinding = (idx: number, key: string, value: any) => {
const newBindings = [...bindings];
newBindings[idx][key] = value;
handleConfigChange("bindings", newBindings);
};
return (
<TabsContent value="binding" className="space-y-4">
<div className="flex items-center justify-between">
<div>
<Label className="text-xs font-medium">Reactive Binding</Label>
<p className="text-[10px] text-muted-foreground mt-1">
-
</p>
</div>
<Button variant="outline" size="sm" onClick={addBinding}>
<Plus className="h-4 w-4 mr-1" />
</Button>
</div>
<Separator />
{bindings.length === 0 && (
<div className="flex flex-col items-center justify-center py-12 text-center">
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-muted">
<Link2 className="h-8 w-8 text-muted-foreground" />
</div>
<h4 className="mb-2 text-sm font-medium"> </h4>
<p className="text-xs text-muted-foreground max-w-[250px]">
"바인딩 추가" .
</p>
</div>
)}
{bindings.map((binding: any, idx: number) => (
<div key={idx} className="space-y-3 rounded-lg border bg-muted/20 p-4">
<div className="flex items-center justify-between">
<Badge variant="outline" className="text-xs"> {idx + 1}</Badge>
<Button variant="ghost" size="sm" onClick={() => removeBinding(idx)}>
<Trash2 className="h-4 w-4" />
</Button>
</div>
{/* 소스 컴포넌트 */}
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> ID</Label>
<Input
value={binding.sourceComponentId || ""}
onChange={(e) => updateBinding(idx, "sourceComponentId", e.target.value)}
placeholder="예: field-1"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Select
value={binding.sourceEvent || "change"}
onValueChange={(v) => updateBinding(idx, "sourceEvent", v)}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="change">change ( )</SelectItem>
<SelectItem value="select">select ()</SelectItem>
<SelectItem value="click">click ()</SelectItem>
<SelectItem value="load">load ()</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> ()</Label>
<Input
value={binding.sourceField || ""}
onChange={(e) => updateBinding(idx, "sourceField", e.target.value)}
placeholder="예: customer_code"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<Separator className="my-4" />
{/* 대상 컴포넌트 */}
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> ID</Label>
<Input
value={binding.targetComponentId || ""}
onChange={(e) => updateBinding(idx, "targetComponentId", e.target.value)}
placeholder="예: dataview-1"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Select
value={binding.targetAction || "filter"}
onValueChange={(v) => updateBinding(idx, "targetAction", v)}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="filter">filter ()</SelectItem>
<SelectItem value="setValue">setValue ( )</SelectItem>
<SelectItem value="show">show ()</SelectItem>
<SelectItem value="hide">hide ()</SelectItem>
<SelectItem value="enable">enable ()</SelectItem>
<SelectItem value="disable">disable ()</SelectItem>
<SelectItem value="refresh">refresh ()</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> ()</Label>
<Input
value={binding.targetField || ""}
onChange={(e) => updateBinding(idx, "targetField", e.target.value)}
placeholder="예: customer_code"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
</div>
))}
</TabsContent>
);
};
// 조건 탭
const renderConditionTab = () => (
<TabsContent value="condition" className="space-y-4">
{/* 표시 조건 */}
<div className="space-y-3">
<div className="flex items-center gap-2">
<Label className="text-xs font-medium"> </Label>
</div>
<p className="text-[10px] text-muted-foreground">
</p>
<Separator className="my-3" />
<div className="space-y-3 rounded-lg border bg-muted/20 p-4">
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Select
value={(config as any).visibilityMode || "always"}
onValueChange={(v) => handleConfigChange("visibilityMode", v)}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="always"> </SelectItem>
<SelectItem value="when_field_equals"> </SelectItem>
<SelectItem value="when_field_not_empty"> </SelectItem>
<SelectItem value="custom"> </SelectItem>
</SelectContent>
</Select>
</div>
{["when_field_equals", "when_field_not_empty", "custom"].includes(
(config as any).visibilityMode
) && (
<>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> ID</Label>
<Input
value={(config as any).visibilityFieldId || ""}
onChange={(e) => handleConfigChange("visibilityFieldId", e.target.value)}
placeholder="예: field-1"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
{(config as any).visibilityMode === "when_field_equals" && (
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Input
value={(config as any).visibilityValue || ""}
onChange={(e) => handleConfigChange("visibilityValue", e.target.value)}
placeholder="예: approved"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
)}
{(config as any).visibilityMode === "custom" && (
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Input
value={(config as any).visibilityExpression || ""}
onChange={(e) => handleConfigChange("visibilityExpression", e.target.value)}
placeholder="예: field1 === 'value' && field2 > 10"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
)}
</>
)}
</div>
</div>
<Separator className="my-6" />
{/* 활성 조건 */}
<div className="space-y-3">
<div className="flex items-center gap-2">
<Label className="text-xs font-medium"> </Label>
</div>
<p className="text-[10px] text-muted-foreground">
(/ )
</p>
<Separator className="my-3" />
<div className="space-y-3 rounded-lg border bg-muted/20 p-4">
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"></Label>
<Select
value={(config as any).enabledMode || "always"}
onValueChange={(v) => handleConfigChange("enabledMode", v)}
>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="always"> </SelectItem>
<SelectItem value="when_field_equals"> </SelectItem>
<SelectItem value="when_field_not_empty"> </SelectItem>
<SelectItem value="custom"> </SelectItem>
</SelectContent>
</Select>
</div>
{["when_field_equals", "when_field_not_empty", "custom"].includes(
(config as any).enabledMode
) && (
<>
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> ID</Label>
<Input
value={(config as any).enabledFieldId || ""}
onChange={(e) => handleConfigChange("enabledFieldId", e.target.value)}
placeholder="예: field-1"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
{(config as any).enabledMode === "when_field_equals" && (
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Input
value={(config as any).enabledValue || ""}
onChange={(e) => handleConfigChange("enabledValue", e.target.value)}
placeholder="예: edit"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
)}
{(config as any).enabledMode === "custom" && (
<div className="space-y-3">
<Label className="text-xs font-medium text-muted-foreground"> </Label>
<Input
value={(config as any).enabledExpression || ""}
onChange={(e) => handleConfigChange("enabledExpression", e.target.value)}
placeholder="예: mode === 'edit' && hasPermission"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
</div>
)}
</>
)}
</div>
</div>
</TabsContent>
);
return (
<div className={cn("flex h-full flex-col", className)}>
{/* 헤더 */}
<div className="flex items-center justify-between border-b bg-muted/30 p-4">
<div className="flex items-center gap-2">
<Settings2 className="h-5 w-5 text-primary" />
<h3 className="text-base font-semibold sm:text-lg"> </h3>
</div>
{/* 간편/상세 토글 (세그먼트 컨트롤 스타일) */}
<div className="inline-flex h-9 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground">
<button
onClick={() => setIsAdvanced(false)}
className={cn(
"inline-flex items-center justify-center gap-1.5 rounded-sm px-3 py-1.5 text-xs font-medium transition-all",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
!isAdvanced
? "bg-background text-foreground shadow-sm"
: "hover:bg-background/50 hover:text-foreground"
)}
>
<Wand2 className="h-3.5 w-3.5" />
</button>
<button
onClick={() => setIsAdvanced(true)}
className={cn(
"inline-flex items-center justify-center gap-1.5 rounded-sm px-3 py-1.5 text-xs font-medium transition-all",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
isAdvanced
? "bg-background text-foreground shadow-sm"
: "hover:bg-background/50 hover:text-foreground"
)}
>
<SlidersHorizontal className="h-3.5 w-3.5" />
</button>
</div>
</div>
{/* Tabs */}
<Tabs defaultValue="basic" className="flex-1 overflow-hidden">
<TabsList className="w-full justify-start border-b rounded-none bg-muted/20 px-4 h-auto">
<TabsTrigger
value="basic"
className="gap-1.5 data-[state=active]:border-b-2 data-[state=active]:border-primary rounded-none pb-3"
>
<Settings className="h-3.5 w-3.5" />
<span className="hidden sm:inline text-xs sm:text-sm"></span>
</TabsTrigger>
<TabsTrigger
value="data"
className="gap-1.5 data-[state=active]:border-b-2 data-[state=active]:border-primary rounded-none pb-3"
>
<Database className="h-3.5 w-3.5" />
<span className="hidden sm:inline text-xs sm:text-sm"></span>
</TabsTrigger>
<TabsTrigger
value="display"
className="gap-1.5 data-[state=active]:border-b-2 data-[state=active]:border-primary rounded-none pb-3"
>
<Palette className="h-3.5 w-3.5" />
<span className="hidden sm:inline text-xs sm:text-sm"></span>
</TabsTrigger>
<TabsTrigger
value="binding"
className="gap-1.5 data-[state=active]:border-b-2 data-[state=active]:border-primary rounded-none pb-3"
>
<Link2 className="h-3.5 w-3.5" />
<span className="hidden sm:inline text-xs sm:text-sm"></span>
</TabsTrigger>
<TabsTrigger
value="condition"
className="gap-1.5 data-[state=active]:border-b-2 data-[state=active]:border-primary rounded-none pb-3"
>
<Eye className="h-3.5 w-3.5" />
<span className="hidden sm:inline text-xs sm:text-sm"></span>
</TabsTrigger>
</TabsList>
<div className="overflow-y-auto p-4">
{renderBasicTab()}
{renderDataTab()}
{renderDisplayTab()}
{renderBindingTab()}
{renderConditionTab()}
</div>
</Tabs>
</div>
);
}