diff --git a/frontend/components/v2/config-panels/V2SelectConfigPanel.tsx b/frontend/components/v2/config-panels/V2SelectConfigPanel.tsx index 47964819..f72b6696 100644 --- a/frontend/components/v2/config-panels/V2SelectConfigPanel.tsx +++ b/frontend/components/v2/config-panels/V2SelectConfigPanel.tsx @@ -2,16 +2,18 @@ /** * V2Select 설정 패널 - * 통합 선택 컴포넌트의 세부 설정을 관리합니다. + * 토스식 단계별 UX: 소스 카드 선택 -> 소스별 설정 -> 고급 설정(접힘) */ import React, { useState, useEffect, useCallback, useMemo } from "react"; -import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Checkbox } from "@/components/ui/checkbox"; +import { Switch } from "@/components/ui/switch"; import { Button } from "@/components/ui/button"; -import { Plus, Trash2, Loader2, Filter } from "lucide-react"; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; +import { Separator } from "@/components/ui/separator"; +import { List, Code, Database, FolderTree, Settings, ChevronDown, Plus, Trash2, Loader2, Filter } from "lucide-react"; +import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; import type { V2SelectFilter } from "@/types/v2-components"; @@ -52,6 +54,35 @@ const USER_FIELD_OPTIONS = [ { value: "userName", label: "사용자명" }, ] as const; +// ─── 데이터 소스 카드 정의 ─── +const SOURCE_CARDS = [ + { + value: "static", + icon: List, + title: "직접 입력", + description: "옵션을 직접 추가해요", + }, + { + value: "code", + icon: Code, + title: "공통 코드", + description: "등록된 공통 코드를 사용해요", + }, + { + value: "entity", + icon: Database, + title: "테이블 데이터", + description: "다른 테이블에서 가져와요", + entityOnly: true, + }, + { + value: "category", + icon: FolderTree, + title: "카테고리", + description: "카테고리로 분류해요", + }, +] as const; + /** * 필터 조건 설정 서브 컴포넌트 */ @@ -108,7 +139,7 @@ const FilterConditionsSection: React.FC<{
- DATA FILTER + 데이터 필터
+ ); + })}
- {/* DATA SOURCE 섹션 */} -
-

DATA SOURCE

-
- 데이터 소스 -
- {isCategoryType ? ( -
- 카테고리 (자동) + {/* ─── 2단계: 소스별 설정 ─── */} + + {/* 직접 입력 (static) */} + {effectiveSource === "static" && ( +
+
+ 옵션 목록 + +
+ + {options.length > 0 ? ( +
+ {options.map((option: any, index: number) => ( +
+ updateOptionValue(index, e.target.value)} + placeholder={`옵션 ${index + 1}`} + className="h-8 flex-1 text-sm" + /> + +
+ ))} +
+ ) : ( +
+ +

아직 옵션이 없어요

+

위의 추가 버튼으로 옵션을 만들어보세요

+
+ )} + + {options.length > 0 && ( +
+
+ 기본 선택값 +
- ) : ( - - )} -
+
+ )}
-
+ )} - {/* CATEGORY 섹션 */} + {/* 공통 코드 (code) */} + {effectiveSource === "code" && ( +
+
+ + 공통 코드 +
+ {config.codeGroup ? ( +
+

코드 그룹

+

{config.codeGroup}

+

+ 테이블 컬럼에 설정된 코드 그룹이 자동으로 적용돼요 +

+
+ ) : ( +
+

코드 그룹이 설정되지 않았어요

+

테이블 타입 관리에서 컬럼의 코드 그룹을 설정해주세요

+
+ )} +
+ )} + + {/* 테이블 데이터 (entity) */} + {effectiveSource === "entity" && ( +
+
+ + 테이블 데이터 +
+ +
+

참조 테이블

+

{config.entityTable || "미설정"}

+
+ + {loadingColumns && ( +
+ + 컬럼 목록 로딩 중... +
+ )} + + {entityColumns.length > 0 && ( +
+
+

실제 저장되는 값

+ +
+
+

사용자에게 보여지는 텍스트

+ +
+

+ 엔티티 선택 시 같은 폼의 관련 필드가 자동으로 채워져요 +

+
+ )} + + {!loadingColumns && entityColumns.length === 0 && !config.entityTable && ( +
+

참조 테이블이 설정되지 않았어요

+

테이블 타입 관리에서 참조 테이블을 설정해주세요

+
+ )} + + {config.entityTable && !loadingColumns && entityColumns.length === 0 && ( +

+ 테이블 컬럼을 조회할 수 없습니다. 테이블 타입 관리에서 참조 테이블을 설정해주세요. +

+ )} +
+ )} + + {/* 카테고리 (category) */} {effectiveSource === "category" && ( -
-

CATEGORY

+
+
+ + 카테고리 +
-
-
+
+
-

테이블

-

{config.categoryTable || tableName || "-"}

+

테이블

+

{config.categoryTable || tableName || "-"}

-

컬럼

-

{config.categoryColumn || columnName || "-"}

+

컬럼

+

{config.categoryColumn || columnName || "-"}

{loadingCategoryValues && ( -
+
카테고리 값 로딩 중...
)} {categoryValues.length > 0 && ( - <> -
- 값 목록 ({categoryValues.length}개) -
- {categoryValues.map((cv) => ( -
- {cv.valueCode} - {cv.valueLabel} -
- ))} -
+
+

+ {categoryValues.length}개의 값이 있어요 +

+
+ {categoryValues.map((cv) => ( +
+ {cv.valueCode} + {cv.valueLabel} +
+ ))}
-
- 기본값 -
- -
+
+ 기본 선택값 +
-

화면 로드 시 자동 선택될 카테고리 값

- +
)} {!loadingCategoryValues && categoryValues.length === 0 && ( -

+

카테고리 값이 없습니다. 테이블 카테고리 관리에서 값을 추가해주세요.

)}
)} - {/* STATIC OPTIONS 섹션 */} - {effectiveSource === "static" && ( -
-
-

STATIC OPTIONS

- -
-
- {options.map((option: any, index: number) => ( -
- updateOptionValue(index, e.target.value)} - placeholder={`옵션 ${index + 1}`} - className="h-7 flex-1 text-xs" - /> - -
- ))} - {options.length === 0 && ( -

옵션을 추가해주세요

- )} -
- - {options.length > 0 && ( - <> -
- 기본값 -
- -
-
-

화면 로드 시 자동 선택될 값

- - )} -
- )} - - {/* CODE GROUP 섹션 */} - {effectiveSource === "code" && ( -
-

CODE GROUP

-
- 코드 그룹 -
- {config.codeGroup ? ( - {config.codeGroup} - ) : ( - 미설정 - )} -
-
-
- )} - - {/* ENTITY 섹션 */} - {effectiveSource === "entity" && ( -
-

ENTITY

- -
- 참조 테이블 -
- -
-
- - {loadingColumns && ( -
- - 컬럼 목록 로딩 중... -
- )} - -
-
- - {entityColumns.length > 0 ? ( - - ) : ( - updateConfig("entityValueColumn", e.target.value)} - placeholder="id" - className="h-7 text-xs" - /> - )} -
-
- - {entityColumns.length > 0 ? ( - - ) : ( - updateConfig("entityLabelColumn", e.target.value)} - placeholder="name" - className="h-7 text-xs" - /> - )} -
-
- - {config.entityTable && !loadingColumns && entityColumns.length === 0 && ( -

- 테이블 컬럼을 조회할 수 없습니다. 테이블 타입 관리에서 참조 테이블을 설정해주세요. -

- )} - - {config.entityTable && entityColumns.length > 0 && ( -

- 같은 폼에 참조 테이블({config.entityTable})의 컬럼이 배치되어 있으면, 엔티티 선택 시 해당 필드가 자동으로 - 채워집니다. -

- )} -
- )} - - {/* OPTIONS 섹션 */} -
-

OPTIONS

- -
- 다중 선택 허용 - updateConfig("multiple", checked)} - /> -
- -
- 검색 기능 - updateConfig("searchable", checked)} - /> -
- -
- 값 초기화 허용 - updateConfig("allowClear", checked)} - /> -
- - {config.multiple && ( -
- 최대 선택 개수 -
- updateConfig("maxSelect", e.target.value ? Number(e.target.value) : undefined)} - placeholder="제한 없음" - min="1" - className="h-7 text-xs" - /> -
-
- )} -
- - {/* DATA FILTER 섹션 */} + {/* 데이터 필터 (static 제외, filterTargetTable 있을 때만) */} {effectiveSource !== "static" && filterTargetTable && ( -
+
= ({ />
)} + + {/* ─── 3단계: 고급 설정 (기본 접혀있음) ─── */} + + + + + +
+ {/* 선택 모드 */} +
+

선택 방식

+ +

대부분의 경우 드롭다운이 적합해요

+
+ + {/* 토글 옵션들 */} +
+
+
+

여러 개 선택

+

한 번에 여러 값을 선택할 수 있어요

+
+ updateConfig("multiple", checked)} + /> +
+ + {config.multiple && ( +
+
+ 최대 선택 개수 + updateConfig("maxSelect", e.target.value ? Number(e.target.value) : undefined)} + placeholder="제한 없음" + min={1} + className="h-7 w-[100px] text-xs" + /> +
+
+ )} + +
+
+

검색 기능

+

옵션이 많을 때 검색으로 찾을 수 있어요

+
+ updateConfig("searchable", checked)} + /> +
+ +
+
+

선택 초기화

+

선택한 값을 지울 수 있는 X 버튼이 표시돼요

+
+ updateConfig("allowClear", checked)} + /> +
+
+
+
+
); };