[agent-pipeline] pipe-20260311071246-rhvz round-4

This commit is contained in:
DDD1542 2026-03-11 16:31:52 +09:00
parent d1d5f651cc
commit 506475e5cc
1 changed files with 201 additions and 225 deletions

View File

@ -9,7 +9,6 @@ import React, { useState, useEffect, useCallback, useMemo } from "react";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Plus, Trash2, Loader2, Filter } from "lucide-react"; import { Plus, Trash2, Loader2, Filter } from "lucide-react";
@ -75,7 +74,6 @@ const FilterConditionsSection: React.FC<{
const updated = [...filters]; const updated = [...filters];
updated[index] = { ...updated[index], ...patch }; updated[index] = { ...updated[index], ...patch };
// valueType 변경 시 관련 필드 초기화
if (patch.valueType) { if (patch.valueType) {
if (patch.valueType === "static") { if (patch.valueType === "static") {
updated[index].fieldRef = undefined; updated[index].fieldRef = undefined;
@ -89,7 +87,6 @@ const FilterConditionsSection: React.FC<{
} }
} }
// isNull/isNotNull 연산자는 값 불필요
if (patch.operator === "isNull" || patch.operator === "isNotNull") { if (patch.operator === "isNull" || patch.operator === "isNotNull") {
updated[index].value = undefined; updated[index].value = undefined;
updated[index].fieldRef = undefined; updated[index].fieldRef = undefined;
@ -107,11 +104,11 @@ const FilterConditionsSection: React.FC<{
const needsValue = (op: string) => op !== "isNull" && op !== "isNotNull"; const needsValue = (op: string) => op !== "isNull" && op !== "isNotNull";
return ( return (
<div className="space-y-3"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<Filter className="h-3.5 w-3.5 text-muted-foreground" /> <Filter className="h-3.5 w-3.5 text-muted-foreground" />
<Label className="text-xs font-medium"> </Label> <span className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">DATA FILTER</span>
</div> </div>
<Button <Button
type="button" type="button"
@ -142,12 +139,10 @@ const FilterConditionsSection: React.FC<{
</p> </p>
)} )}
<div className="space-y-3"> <div className="space-y-2">
{filters.map((filter, index) => ( {filters.map((filter, index) => (
<div key={index} className="space-y-1.5 rounded-md border p-2"> <div key={index} className="space-y-1.5 rounded-md border p-2">
{/* 행 1: 컬럼 + 연산자 + 삭제 */}
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
{/* 컬럼 선택 */}
<Select <Select
value={filter.column || ""} value={filter.column || ""}
onValueChange={(v) => updateFilter(index, { column: v })} onValueChange={(v) => updateFilter(index, { column: v })}
@ -164,7 +159,6 @@ const FilterConditionsSection: React.FC<{
</SelectContent> </SelectContent>
</Select> </Select>
{/* 연산자 선택 */}
<Select <Select
value={filter.operator || "="} value={filter.operator || "="}
onValueChange={(v) => updateFilter(index, { operator: v as V2SelectFilter["operator"] })} onValueChange={(v) => updateFilter(index, { operator: v as V2SelectFilter["operator"] })}
@ -181,7 +175,6 @@ const FilterConditionsSection: React.FC<{
</SelectContent> </SelectContent>
</Select> </Select>
{/* 삭제 버튼 */}
<Button <Button
type="button" type="button"
variant="ghost" variant="ghost"
@ -193,10 +186,8 @@ const FilterConditionsSection: React.FC<{
</Button> </Button>
</div> </div>
{/* 행 2: 값 유형 + 값 입력 (isNull/isNotNull 제외) */}
{needsValue(filter.operator) && ( {needsValue(filter.operator) && (
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
{/* 값 유형 */}
<Select <Select
value={filter.valueType || "static"} value={filter.valueType || "static"}
onValueChange={(v) => updateFilter(index, { valueType: v as V2SelectFilter["valueType"] })} onValueChange={(v) => updateFilter(index, { valueType: v as V2SelectFilter["valueType"] })}
@ -213,7 +204,6 @@ const FilterConditionsSection: React.FC<{
</SelectContent> </SelectContent>
</Select> </Select>
{/* 값 입력 영역 */}
{(filter.valueType || "static") === "static" && ( {(filter.valueType || "static") === "static" && (
<Input <Input
value={String(filter.value ?? "")} value={String(filter.value ?? "")}
@ -261,11 +251,8 @@ const FilterConditionsSection: React.FC<{
interface V2SelectConfigPanelProps { interface V2SelectConfigPanelProps {
config: Record<string, any>; config: Record<string, any>;
onChange: (config: Record<string, any>) => void; onChange: (config: Record<string, any>) => void;
/** 컬럼의 inputType (entity/category 타입 확인용) */
inputType?: string; inputType?: string;
/** 현재 테이블명 (카테고리 값 조회용) */
tableName?: string; tableName?: string;
/** 현재 컬럼명 (카테고리 값 조회용) */
columnName?: string; columnName?: string;
} }
@ -282,11 +269,9 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({
const [entityColumns, setEntityColumns] = useState<ColumnOption[]>([]); const [entityColumns, setEntityColumns] = useState<ColumnOption[]>([]);
const [loadingColumns, setLoadingColumns] = useState(false); const [loadingColumns, setLoadingColumns] = useState(false);
// 카테고리 값 목록
const [categoryValues, setCategoryValues] = useState<CategoryValueOption[]>([]); const [categoryValues, setCategoryValues] = useState<CategoryValueOption[]>([]);
const [loadingCategoryValues, setLoadingCategoryValues] = useState(false); const [loadingCategoryValues, setLoadingCategoryValues] = useState(false);
// 필터용 컬럼 목록 (옵션 데이터 소스 테이블의 컬럼)
const [filterColumns, setFilterColumns] = useState<ColumnOption[]>([]); const [filterColumns, setFilterColumns] = useState<ColumnOption[]>([]);
const [loadingFilterColumns, setLoadingFilterColumns] = useState(false); const [loadingFilterColumns, setLoadingFilterColumns] = useState(false);
@ -294,7 +279,6 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({
onChange({ ...config, [field]: value }); onChange({ ...config, [field]: value });
}; };
// 필터 대상 테이블 결정
const filterTargetTable = useMemo(() => { const filterTargetTable = useMemo(() => {
const src = config.source || "static"; const src = config.source || "static";
if (src === "entity") return config.entityTable; if (src === "entity") return config.entityTable;
@ -303,7 +287,6 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({
return null; return null;
}, [config.source, config.entityTable, config.table, tableName]); }, [config.source, config.entityTable, config.table, tableName]);
// 필터 대상 테이블의 컬럼 로드
useEffect(() => { useEffect(() => {
if (!filterTargetTable) { if (!filterTargetTable) {
setFilterColumns([]); setFilterColumns([]);
@ -332,14 +315,12 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({
loadFilterColumns(); loadFilterColumns();
}, [filterTargetTable]); }, [filterTargetTable]);
// 카테고리 타입이면 source를 자동으로 category로 설정
useEffect(() => { useEffect(() => {
if (isCategoryType && config.source !== "category") { if (isCategoryType && config.source !== "category") {
onChange({ ...config, source: "category" }); onChange({ ...config, source: "category" });
} }
}, [isCategoryType]); }, [isCategoryType]);
// 카테고리 값 로드
const loadCategoryValues = useCallback(async (catTable: string, catColumn: string) => { const loadCategoryValues = useCallback(async (catTable: string, catColumn: string) => {
if (!catTable || !catColumn) { if (!catTable || !catColumn) {
setCategoryValues([]); setCategoryValues([]);
@ -374,7 +355,6 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({
} }
}, []); }, []);
// 카테고리 소스일 때 값 로드
useEffect(() => { useEffect(() => {
if (config.source === "category") { if (config.source === "category") {
const catTable = config.categoryTable || tableName; const catTable = config.categoryTable || tableName;
@ -385,7 +365,6 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({
} }
}, [config.source, config.categoryTable, config.categoryColumn, tableName, columnName, loadCategoryValues]); }, [config.source, config.categoryTable, config.categoryColumn, tableName, columnName, loadCategoryValues]);
// 엔티티 테이블 변경 시 컬럼 목록 조회
const loadEntityColumns = useCallback(async (tblName: string) => { const loadEntityColumns = useCallback(async (tblName: string) => {
if (!tblName) { if (!tblName) {
setEntityColumns([]); setEntityColumns([]);
@ -423,7 +402,6 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({
} }
}, [config.source, config.entityTable, loadEntityColumns]); }, [config.source, config.entityTable, loadEntityColumns]);
// 정적 옵션 관리
const options = config.options || []; const options = config.options || [];
const addOption = () => { const addOption = () => {
@ -442,135 +420,139 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({
updateConfig("options", newOptions); updateConfig("options", newOptions);
}; };
// 현재 source 결정 (카테고리 타입이면 강제 category)
const effectiveSource = isCategoryType ? "category" : config.source || "static"; const effectiveSource = isCategoryType ? "category" : config.source || "static";
return ( return (
<div className="space-y-4"> <div className="space-y-1">
{/* 선택 모드 */} {/* SELECT MODE 섹션 */}
<div className="space-y-2"> <div className="border-b border-border/50 pb-3 mb-3">
<Label className="text-xs font-medium"> </Label> <h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">SELECT MODE</h4>
<Select value={config.mode || "dropdown"} onValueChange={(value) => updateConfig("mode", value)}> <div className="flex items-center justify-between py-1.5">
<SelectTrigger className="h-8 text-xs"> <span className="text-xs text-muted-foreground"> </span>
<SelectValue placeholder="모드 선택" /> <div className="w-[140px]">
</SelectTrigger> <Select value={config.mode || "dropdown"} onValueChange={(value) => updateConfig("mode", value)}>
<SelectContent> <SelectTrigger className="h-7 text-xs">
<SelectItem value="dropdown"></SelectItem> <SelectValue placeholder="모드 선택" />
<SelectItem value="combobox"> ()</SelectItem> </SelectTrigger>
<SelectItem value="radio"> </SelectItem> <SelectContent>
<SelectItem value="check"></SelectItem> <SelectItem value="dropdown"></SelectItem>
<SelectItem value="tag"> </SelectItem> <SelectItem value="combobox"> ()</SelectItem>
<SelectItem value="tagbox"> (+)</SelectItem> <SelectItem value="radio"> </SelectItem>
<SelectItem value="toggle"> </SelectItem> <SelectItem value="check"></SelectItem>
<SelectItem value="swap"> </SelectItem> <SelectItem value="tag"> </SelectItem>
</SelectContent> <SelectItem value="tagbox"> (+)</SelectItem>
</Select> <SelectItem value="toggle"> </SelectItem>
</div> <SelectItem value="swap"> </SelectItem>
</SelectContent>
<Separator /> </Select>
{/* 데이터 소스 */}
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
{isCategoryType ? (
<div className="bg-muted flex h-8 items-center rounded-md px-3">
<span className="text-xs font-medium text-emerald-600"> ( )</span>
</div> </div>
) : ( </div>
<Select value={config.source || "static"} onValueChange={(value) => updateConfig("source", value)}>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="소스 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="static"> </SelectItem>
<SelectItem value="code"> </SelectItem>
<SelectItem value="category"></SelectItem>
{isEntityType && <SelectItem value="entity"></SelectItem>}
</SelectContent>
</Select>
)}
</div> </div>
{/* 카테고리 설정 */} {/* DATA SOURCE 섹션 */}
<div className="border-b border-border/50 pb-3 mb-3">
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">DATA SOURCE</h4>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<div className="w-[140px]">
{isCategoryType ? (
<div className="bg-muted flex h-7 items-center rounded-md px-2">
<span className="text-[11px] font-medium text-emerald-600"> ()</span>
</div>
) : (
<Select value={config.source || "static"} onValueChange={(value) => updateConfig("source", value)}>
<SelectTrigger className="h-7 text-xs">
<SelectValue placeholder="소스 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="static"> </SelectItem>
<SelectItem value="code"> </SelectItem>
<SelectItem value="category"></SelectItem>
{isEntityType && <SelectItem value="entity"></SelectItem>}
</SelectContent>
</Select>
)}
</div>
</div>
</div>
{/* CATEGORY 섹션 */}
{effectiveSource === "category" && ( {effectiveSource === "category" && (
<div className="space-y-3"> <div className="border-b border-border/50 pb-3 mb-3">
<div className="space-y-1"> <h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">CATEGORY</h4>
<Label className="text-xs font-medium"> </Label>
<div className="bg-muted rounded-md p-2"> <div className="bg-muted rounded-md p-2 mb-2">
<div className="grid grid-cols-2 gap-2"> <div className="flex gap-4">
<div> <div>
<p className="text-muted-foreground text-[10px]"></p> <p className="text-muted-foreground text-[10px]"></p>
<p className="text-xs font-medium">{config.categoryTable || tableName || "-"}</p> <p className="text-xs font-medium">{config.categoryTable || tableName || "-"}</p>
</div> </div>
<div> <div>
<p className="text-muted-foreground text-[10px]"></p> <p className="text-muted-foreground text-[10px]"></p>
<p className="text-xs font-medium">{config.categoryColumn || columnName || "-"}</p> <p className="text-xs font-medium">{config.categoryColumn || columnName || "-"}</p>
</div>
</div> </div>
</div> </div>
</div> </div>
{/* 카테고리 값 로딩 중 */}
{loadingCategoryValues && ( {loadingCategoryValues && (
<div className="text-muted-foreground flex items-center gap-2 text-xs"> <div className="text-muted-foreground flex items-center gap-2 text-xs py-1">
<Loader2 className="h-3 w-3 animate-spin" /> <Loader2 className="h-3 w-3 animate-spin" />
... ...
</div> </div>
)} )}
{/* 카테고리 값 목록 표시 */}
{categoryValues.length > 0 && ( {categoryValues.length > 0 && (
<div className="space-y-2"> <>
<Label className="text-xs font-medium"> ({categoryValues.length})</Label> <div className="py-1">
<div className="bg-muted max-h-32 space-y-0.5 overflow-y-auto rounded-md p-1.5"> <span className="text-[10px] text-muted-foreground"> ({categoryValues.length})</span>
{categoryValues.map((cv) => ( <div className="bg-muted max-h-32 space-y-0.5 overflow-y-auto rounded-md p-1.5 mt-1">
<div key={cv.valueCode} className="flex items-center gap-2 px-1.5 py-0.5">
<span className="text-muted-foreground shrink-0 font-mono text-[10px]">{cv.valueCode}</span>
<span className="truncate text-xs">{cv.valueLabel}</span>
</div>
))}
</div>
</div>
)}
{/* 기본값 설정 */}
{categoryValues.length > 0 && (
<div className="border-t pt-2">
<Label className="text-xs font-medium"></Label>
<Select
value={config.defaultValue || "_none_"}
onValueChange={(value) => updateConfig("defaultValue", value === "_none_" ? "" : value)}
>
<SelectTrigger className="mt-1 h-8 text-xs">
<SelectValue placeholder="기본값 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="_none_"> </SelectItem>
{categoryValues.map((cv) => ( {categoryValues.map((cv) => (
<SelectItem key={cv.valueCode} value={cv.valueCode}> <div key={cv.valueCode} className="flex items-center gap-2 px-1.5 py-0.5">
{cv.valueLabel} <span className="text-muted-foreground shrink-0 font-mono text-[10px]">{cv.valueCode}</span>
</SelectItem> <span className="truncate text-xs">{cv.valueLabel}</span>
</div>
))} ))}
</SelectContent> </div>
</Select> </div>
<p className="text-muted-foreground mt-1 text-[10px]"> </p>
</div> <div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"></span>
<div className="w-[140px]">
<Select
value={config.defaultValue || "_none_"}
onValueChange={(value) => updateConfig("defaultValue", value === "_none_" ? "" : value)}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue placeholder="기본값 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="_none_"> </SelectItem>
{categoryValues.map((cv) => (
<SelectItem key={cv.valueCode} value={cv.valueCode}>
{cv.valueLabel}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<p className="text-muted-foreground text-[10px] mt-0.5"> </p>
</>
)} )}
{/* 카테고리 값 없음 안내 */}
{!loadingCategoryValues && categoryValues.length === 0 && ( {!loadingCategoryValues && categoryValues.length === 0 && (
<p className="text-[10px] text-amber-600"> <p className="text-[10px] text-amber-600 py-1">
. . . .
</p> </p>
)} )}
</div> </div>
)} )}
{/* 정적 옵션 관리 */} {/* STATIC OPTIONS 섹션 */}
{effectiveSource === "static" && ( {effectiveSource === "static" && (
<div className="space-y-2"> <div className="border-b border-border/50 pb-3 mb-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between py-2">
<Label className="text-xs font-medium"> </Label> <h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">STATIC OPTIONS</h4>
<Button type="button" variant="ghost" size="sm" onClick={addOption} className="h-6 px-2 text-xs"> <Button type="button" variant="ghost" size="sm" onClick={addOption} className="h-6 px-2 text-xs">
<Plus className="mr-1 h-3 w-3" /> <Plus className="mr-1 h-3 w-3" />
@ -601,77 +583,86 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({
)} )}
</div> </div>
{/* 기본값 설정 */}
{options.length > 0 && ( {options.length > 0 && (
<div className="mt-3 border-t pt-2"> <>
<Label className="text-xs font-medium"></Label> <div className="flex items-center justify-between py-1.5 mt-2">
<Select <span className="text-xs text-muted-foreground"></span>
value={config.defaultValue || "_none_"} <div className="w-[140px]">
onValueChange={(value) => updateConfig("defaultValue", value === "_none_" ? "" : value)} <Select
> value={config.defaultValue || "_none_"}
<SelectTrigger className="mt-1 h-8 text-xs"> onValueChange={(value) => updateConfig("defaultValue", value === "_none_" ? "" : value)}
<SelectValue placeholder="기본값 선택" /> >
</SelectTrigger> <SelectTrigger className="h-7 text-xs">
<SelectContent> <SelectValue placeholder="기본값 선택" />
<SelectItem value="_none_"> </SelectItem> </SelectTrigger>
{options.map((option: any, index: number) => ( <SelectContent>
<SelectItem key={`default-${index}`} value={option.value || `_idx_${index}`}> <SelectItem value="_none_"> </SelectItem>
{option.label || option.value || `옵션 ${index + 1}`} {options.map((option: any, index: number) => (
</SelectItem> <SelectItem key={`default-${index}`} value={option.value || `_idx_${index}`}>
))} {option.label || option.value || `옵션 ${index + 1}`}
</SelectContent> </SelectItem>
</Select> ))}
<p className="text-muted-foreground mt-1 text-[10px]"> </p> </SelectContent>
</div> </Select>
</div>
</div>
<p className="text-muted-foreground text-[10px] mt-0.5"> </p>
</>
)} )}
</div> </div>
)} )}
{/* 공통 코드 설정 */} {/* CODE GROUP 섹션 */}
{effectiveSource === "code" && ( {effectiveSource === "code" && (
<div className="space-y-1"> <div className="border-b border-border/50 pb-3 mb-3">
<Label className="text-xs font-medium"> </Label> <h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">CODE GROUP</h4>
{config.codeGroup ? ( <div className="flex items-center justify-between py-1.5">
<p className="text-foreground text-sm font-medium">{config.codeGroup}</p> <span className="text-xs text-muted-foreground"> </span>
) : ( <div className="w-[140px]">
<p className="text-xs text-amber-600"> </p> {config.codeGroup ? (
)} <span className="text-xs font-medium">{config.codeGroup}</span>
) : (
<span className="text-[10px] text-amber-600"></span>
)}
</div>
</div>
</div> </div>
)} )}
{/* 엔티티(참조 테이블) 설정 */} {/* ENTITY 섹션 */}
{effectiveSource === "entity" && ( {effectiveSource === "entity" && (
<div className="space-y-3"> <div className="border-b border-border/50 pb-3 mb-3">
<div className="space-y-2"> <h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">ENTITY</h4>
<Label className="text-xs font-medium"> </Label>
<Input <div className="flex items-center justify-between py-1.5">
value={config.entityTable || ""} <span className="text-xs text-muted-foreground"> </span>
readOnly <div className="w-[140px]">
disabled <Input
placeholder="테이블 타입 관리에서 설정" value={config.entityTable || ""}
className="bg-muted h-8 text-xs" readOnly
/> disabled
<p className="text-muted-foreground text-[10px]"> placeholder="미설정"
( ) className="bg-muted h-7 text-xs"
</p> />
</div>
</div> </div>
{loadingColumns && ( {loadingColumns && (
<div className="text-muted-foreground flex items-center gap-2 text-xs"> <div className="text-muted-foreground flex items-center gap-2 text-xs py-1">
<Loader2 className="h-3 w-3 animate-spin" /> <Loader2 className="h-3 w-3 animate-spin" />
... ...
</div> </div>
)} )}
<div className="grid grid-cols-2 gap-2"> <div className="flex gap-2 mt-1">
<div className="space-y-2"> <div className="flex-1">
<Label className="text-xs font-medium"> ()</Label> <Label className="text-[10px] text-muted-foreground"> </Label>
{entityColumns.length > 0 ? ( {entityColumns.length > 0 ? (
<Select <Select
value={config.entityValueColumn || ""} value={config.entityValueColumn || ""}
onValueChange={(value) => updateConfig("entityValueColumn", value)} onValueChange={(value) => updateConfig("entityValueColumn", value)}
> >
<SelectTrigger className="h-8 text-xs"> <SelectTrigger className="h-7 text-xs">
<SelectValue placeholder="컬럼 선택" /> <SelectValue placeholder="컬럼 선택" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -687,19 +678,18 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({
value={config.entityValueColumn || ""} value={config.entityValueColumn || ""}
onChange={(e) => updateConfig("entityValueColumn", e.target.value)} onChange={(e) => updateConfig("entityValueColumn", e.target.value)}
placeholder="id" placeholder="id"
className="h-8 text-xs" className="h-7 text-xs"
/> />
)} )}
<p className="text-muted-foreground text-[10px]"> </p>
</div> </div>
<div className="space-y-2"> <div className="flex-1">
<Label className="text-xs font-medium"> </Label> <Label className="text-[10px] text-muted-foreground"> </Label>
{entityColumns.length > 0 ? ( {entityColumns.length > 0 ? (
<Select <Select
value={config.entityLabelColumn || ""} value={config.entityLabelColumn || ""}
onValueChange={(value) => updateConfig("entityLabelColumn", value)} onValueChange={(value) => updateConfig("entityLabelColumn", value)}
> >
<SelectTrigger className="h-8 text-xs"> <SelectTrigger className="h-7 text-xs">
<SelectValue placeholder="컬럼 선택" /> <SelectValue placeholder="컬럼 선택" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -715,89 +705,75 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({
value={config.entityLabelColumn || ""} value={config.entityLabelColumn || ""}
onChange={(e) => updateConfig("entityLabelColumn", e.target.value)} onChange={(e) => updateConfig("entityLabelColumn", e.target.value)}
placeholder="name" placeholder="name"
className="h-8 text-xs" className="h-7 text-xs"
/> />
)} )}
<p className="text-muted-foreground text-[10px]"> </p>
</div> </div>
</div> </div>
{config.entityTable && !loadingColumns && entityColumns.length === 0 && ( {config.entityTable && !loadingColumns && entityColumns.length === 0 && (
<p className="text-[10px] text-amber-600"> <p className="text-[10px] text-amber-600 mt-1">
. . . .
</p> </p>
)} )}
{config.entityTable && entityColumns.length > 0 && ( {config.entityTable && entityColumns.length > 0 && (
<div className="border-t pt-3"> <p className="text-muted-foreground text-[10px] mt-2">
<p className="text-muted-foreground text-[10px]"> ({config.entityTable}) ,
({config.entityTable}) , .
. </p>
</p>
</div>
)} )}
</div> </div>
)} )}
<Separator /> {/* OPTIONS 섹션 */}
<div className="border-b border-border/50 pb-3 mb-3">
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">OPTIONS</h4>
{/* 추가 옵션 */} <div className="flex items-center justify-between py-1.5">
<div className="space-y-3"> <span className="text-xs text-muted-foreground"> </span>
<Label className="text-xs font-medium"> </Label>
<div className="flex items-center space-x-2">
<Checkbox <Checkbox
id="multiple"
checked={config.multiple || false} checked={config.multiple || false}
onCheckedChange={(checked) => updateConfig("multiple", checked)} onCheckedChange={(checked) => updateConfig("multiple", checked)}
/> />
<label htmlFor="multiple" className="text-xs">
</label>
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<Checkbox <Checkbox
id="searchable"
checked={config.searchable || false} checked={config.searchable || false}
onCheckedChange={(checked) => updateConfig("searchable", checked)} onCheckedChange={(checked) => updateConfig("searchable", checked)}
/> />
<label htmlFor="searchable" className="text-xs">
</label>
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<Checkbox <Checkbox
id="allowClear"
checked={config.allowClear !== false} checked={config.allowClear !== false}
onCheckedChange={(checked) => updateConfig("allowClear", checked)} onCheckedChange={(checked) => updateConfig("allowClear", checked)}
/> />
<label htmlFor="allowClear" className="text-xs">
</label>
</div> </div>
{config.multiple && (
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<div className="w-[140px]">
<Input
type="number"
value={config.maxSelect ?? ""}
onChange={(e) => updateConfig("maxSelect", e.target.value ? Number(e.target.value) : undefined)}
placeholder="제한 없음"
min="1"
className="h-7 text-xs"
/>
</div>
</div>
)}
</div> </div>
{/* 다중 선택 시 최대 개수 */} {/* DATA FILTER 섹션 */}
{config.multiple && (
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
<Input
type="number"
value={config.maxSelect ?? ""}
onChange={(e) => updateConfig("maxSelect", e.target.value ? Number(e.target.value) : undefined)}
placeholder="제한 없음"
min="1"
className="h-8 text-xs"
/>
</div>
)}
{/* 데이터 필터 조건 - static 소스 외 모든 소스에서 사용 */}
{effectiveSource !== "static" && filterTargetTable && ( {effectiveSource !== "static" && filterTargetTable && (
<> <div className="border-b border-border/50 pb-3 mb-3">
<Separator />
<FilterConditionsSection <FilterConditionsSection
filters={(config.filters as V2SelectFilter[]) || []} filters={(config.filters as V2SelectFilter[]) || []}
columns={filterColumns} columns={filterColumns}
@ -805,7 +781,7 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({
targetTable={filterTargetTable} targetTable={filterTargetTable}
onFiltersChange={(filters) => updateConfig("filters", filters)} onFiltersChange={(filters) => updateConfig("filters", filters)}
/> />
</> </div>
)} )}
</div> </div>
); );