[agent-pipeline] pipe-20260311071246-rhvz round-4
This commit is contained in:
parent
d1d5f651cc
commit
506475e5cc
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue