diff --git a/frontend/components/v2/config-panels/V2RepeaterConfigPanel.tsx b/frontend/components/v2/config-panels/V2RepeaterConfigPanel.tsx index 8ccd9d44..73efa3d1 100644 --- a/frontend/components/v2/config-panels/V2RepeaterConfigPanel.tsx +++ b/frontend/components/v2/config-panels/V2RepeaterConfigPanel.tsx @@ -11,11 +11,12 @@ import React, { useState, useEffect, useMemo, useCallback } from "react"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -// Separator 제거 - 팔란티어 스타일 섹션 헤더 사용 +import { Switch } from "@/components/ui/switch"; import { Checkbox } from "@/components/ui/checkbox"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Database, Link2, @@ -31,7 +32,10 @@ import { Wand2, Check, ChevronsUpDown, - ListTree, + Settings, + Rows3, + Columns3, + MousePointerClick, } from "lucide-react"; import { Command, @@ -55,10 +59,7 @@ import { cn } from "@/lib/utils"; import { V2RepeaterConfig, RepeaterColumnConfig, - RepeaterEntityJoin, DEFAULT_REPEATER_CONFIG, - RENDER_MODE_OPTIONS, - MODAL_SIZE_OPTIONS, } from "@/types/v2-repeater"; // 테이블 엔티티 관계 정보 @@ -759,7 +760,7 @@ export const V2RepeaterConfigPanel: React.FC = ({ }, [currentTableColumns, config.dataSource?.foreignKey]); return ( -
+
기본 @@ -768,63 +769,69 @@ export const V2RepeaterConfigPanel: React.FC = ({ {/* 기본 설정 탭 */} - - {/* 렌더링 모드 */} -
-

RENDER MODE

- + + {/* 렌더링 모드 - 카드 선택 */} +
+

리피터를 어떤 방식으로 사용하나요?

+
+ {[ + { value: "inline", icon: Rows3, title: "직접 입력", description: "테이블 컬럼에 바로 입력해요" }, + { value: "modal", icon: Columns3, title: "모달 선택", description: "엔티티를 검색해서 추가해요" }, + { value: "button", icon: MousePointerClick, title: "버튼 연결", description: "버튼으로 관련 화면을 열어요" }, + ].map((card) => { + const Icon = card.icon; + const isSelected = config.renderMode === card.value; + return ( + + ); + })} +
{/* 저장 대상 테이블 */} -
-

SAVE TABLE

+
+
+ + 데이터를 어디에 저장하나요? +
{/* 현재 선택된 테이블 표시 (기존 테이블 UI와 동일한 스타일) */}
= ({ {config.useCustomTable && config.mainTableName && !currentTableName && (

- 독립 저장 모드: 화면 테이블 없이 직접 저장합니다. + 독립 저장 모드: 화면 테이블 없이 직접 저장해요 +

+
+ )} + + {/* 현재 화면 정보 (메인 테이블이 설정된 경우에만 표시) */} + {currentTableName && ( +
+

화면 메인 테이블

+

{currentTableName}

+

+ 컬럼 {currentTableColumns.length}개 / 엔티티 {entityColumns.length}개

)}
- {/* 현재 화면 정보 (메인 테이블이 설정된 경우에만 표시) */} - {currentTableName && ( -
-

MAIN TABLE

-
-

{currentTableName}

-

- 컬럼 {currentTableColumns.length}개 / 엔티티 {entityColumns.length}개 -

-
-
- )} - {/* 모달 모드: 엔티티 컬럼 선택 */} {isModalMode && ( <> -
-

ENTITY SELECT

-

- 모달에서 검색할 엔티티를 선택하세요 (FK만 저장됨) +

+
+ + 어떤 엔티티를 검색하나요? +
+

+ 모달에서 검색할 엔티티를 선택하면 FK만 저장돼요

{entityColumns.length > 0 ? ( @@ -1060,222 +1068,253 @@ export const V2RepeaterConfigPanel: React.FC = ({ ) : ( -
-

+

+

{loadingColumns - ? "로딩 중..." + ? "컬럼 정보를 불러오고 있어요..." : !targetTableForColumns - ? "저장 테이블을 먼저 선택하세요" - : "엔티티 타입 컬럼이 없습니다"} + ? "저장 테이블을 먼저 선택해주세요" + : "엔티티 타입 컬럼이 없어요"}

)} {/* 선택된 엔티티 정보 */} {config.dataSource?.sourceTable && ( -
-

선택된 엔티티

-
-

검색 테이블: {config.dataSource.sourceTable}

-

저장 컬럼: {config.dataSource.foreignKey} (FK)

-
+
+

선택된 엔티티

+

{config.dataSource.sourceTable}

+

+ {config.dataSource.foreignKey} 컬럼에 FK로 저장돼요 +

)}
)} - {/* 소스 디테일 자동 조회 설정 */} -
-

SOURCE DETAIL

-
- - - 소스 디테일 자동 조회 - - { - if (checked) { - updateConfig({ - sourceDetailConfig: { - tableName: "", - foreignKey: "", - parentKey: "", - }, - }); - } else { - updateConfig({ sourceDetailConfig: undefined }); - } - }} - /> -
-

- 모달에서 전달받은 마스터 데이터의 디테일 행을 자동으로 조회하여 리피터에 채웁니다. -

- - {config.sourceDetailConfig && ( -
-
- - - - - - - - - - 테이블을 찾을 수 없습니다. - - {allTables.map((table) => ( - { - updateConfig({ - sourceDetailConfig: { - ...config.sourceDetailConfig!, - tableName: table.tableName, - }, - }); - }} - className="text-xs" - > - - {table.displayName} - - ))} - - - - - + {/* 기능 옵션 - 토스식 Switch + 설명 */} +
+ 기능 옵션 +
+
+
+

추가 버튼

+

새로운 행을 추가할 수 있어요

- -
-
- - - updateConfig({ - sourceDetailConfig: { - ...config.sourceDetailConfig!, - foreignKey: e.target.value, - }, - }) - } - placeholder="예: order_no" - className="h-7 text-xs" - /> -
-
- - - updateConfig({ - sourceDetailConfig: { - ...config.sourceDetailConfig!, - parentKey: e.target.value, - }, - }) - } - placeholder="예: order_no" - className="h-7 text-xs" - /> -
-
- -

- 마스터에서 [{config.sourceDetailConfig.parentKey || "?"}] 추출 → - {" "}{config.sourceDetailConfig.tableName || "?"}.{config.sourceDetailConfig.foreignKey || "?"} 로 조회 -

+ updateFeatures("showAddButton", checked)} + />
- )} +
+
+

삭제 버튼

+

선택한 행을 삭제할 수 있어요

+
+ updateFeatures("showDeleteButton", checked)} + /> +
+
+
+

인라인 편집

+

셀을 클릭하면 바로 수정할 수 있어요

+
+ updateFeatures("inlineEdit", checked)} + /> +
+
+
+

다중 선택

+

여러 행을 동시에 선택할 수 있어요

+
+ updateFeatures("multiSelect", checked)} + /> +
+
+
+

행 번호

+

각 행에 순번을 표시해요

+
+ updateFeatures("showRowNumber", checked)} + /> +
+
+
+

행 선택

+

체크박스로 행을 선택할 수 있어요

+
+ updateFeatures("selectable", checked)} + /> +
+
- {/* 기능 옵션 */} -
-

FEATURES

-
- 추가 버튼 - updateFeatures("showAddButton", !!checked)} - /> -
-
- 삭제 버튼 - updateFeatures("showDeleteButton", !!checked)} - /> -
-
- 인라인 편집 - updateFeatures("inlineEdit", !!checked)} - /> -
-
- 다중 선택 - updateFeatures("multiSelect", !!checked)} - /> -
-
- 행 번호 - updateFeatures("showRowNumber", !!checked)} - /> -
-
- 행 선택 - updateFeatures("selectable", !!checked)} - /> -
-
+ {/* 고급 설정 - Collapsible (소스 디테일 등) */} + + + + + +
+ {/* 소스 디테일 자동 조회 */} +
+
+

소스 디테일 자동 조회

+

마스터 데이터의 디테일 행을 자동으로 채워요

+
+ { + if (checked) { + updateConfig({ + sourceDetailConfig: { + tableName: "", + foreignKey: "", + parentKey: "", + }, + }); + } else { + updateConfig({ sourceDetailConfig: undefined }); + } + }} + /> +
+ + {config.sourceDetailConfig && ( +
+
+

디테일 테이블

+ + + + + + + + + 테이블을 찾을 수 없습니다. + + {allTables.map((table) => ( + { + updateConfig({ + sourceDetailConfig: { + ...config.sourceDetailConfig!, + tableName: table.tableName, + }, + }); + }} + className="text-xs" + > + + {table.displayName} + + ))} + + + + + +
+ +
+
+

디테일 FK 컬럼

+ + updateConfig({ + sourceDetailConfig: { + ...config.sourceDetailConfig!, + foreignKey: e.target.value, + }, + }) + } + placeholder="예: order_no" + className="h-7 text-xs" + /> +
+
+

마스터 키 컬럼

+ + updateConfig({ + sourceDetailConfig: { + ...config.sourceDetailConfig!, + parentKey: e.target.value, + }, + }) + } + placeholder="예: order_no" + className="h-7 text-xs" + /> +
+
+ +

+ 마스터에서 [{config.sourceDetailConfig.parentKey || "?"}] 추출 → + {" "}{config.sourceDetailConfig.tableName || "?"}.{config.sourceDetailConfig.foreignKey || "?"} 로 조회해요 +

+
+ )} +
+
+
{/* 컬럼 설정 탭 */} - + {/* 통합 컬럼 선택 */} -
-

COLUMN SELECT

-

- {isModalMode - ? "표시할 컬럼과 입력 컬럼을 선택하세요. 아이콘으로 표시/입력 구분" - : "입력받을 컬럼을 선택하세요" +

+
+ + + {isModalMode ? "어떤 컬럼을 표시하고 입력받을까요?" : "어떤 컬럼을 입력받을까요?"} + +
+

+ {isModalMode + ? "소스 테이블 컬럼은 표시용, 저장 테이블 컬럼은 입력용이에요" + : "체크한 컬럼이 리피터에 입력 필드로 표시돼요" } -

+

{/* 모달 모드: 소스 테이블 컬럼 (표시용) */} {isModalMode && config.dataSource?.sourceTable && ( @@ -1353,11 +1392,11 @@ export const V2RepeaterConfigPanel: React.FC = ({ {/* 선택된 컬럼 상세 설정 */} {config.columns.length > 0 && ( <> -
-

- SELECTED COLUMNS ({config.columns.length}) - 드래그로 순서 변경 -

+
+
+ 선택된 컬럼 ({config.columns.length}) + 드래그로 순서 변경 +
{config.columns.map((col, index) => (
@@ -1684,11 +1723,14 @@ export const V2RepeaterConfigPanel: React.FC = ({ {/* 계산 규칙 */} {(isModalMode || isInlineMode) && config.columns.length > 0 && ( <> -
+
-

CALCULATION RULES

-
@@ -1785,9 +1827,11 @@ export const V2RepeaterConfigPanel: React.FC = ({ ))} {calculationRules.length === 0 && ( -

- 계산 규칙이 없습니다 -

+
+ +

아직 계산 규칙이 없어요

+

위의 추가 버튼으로 수식을 만들어보세요

+
)}
@@ -1796,22 +1840,34 @@ export const V2RepeaterConfigPanel: React.FC = ({ {/* Entity 조인 설정 탭 */} - -
-

ENTITY JOIN

-

- FK 컬럼을 기반으로 참조 테이블의 데이터를 자동으로 조회하여 표시합니다 + +

+
+ + 연결된 테이블 데이터 표시 +
+

+ FK 컬럼을 기반으로 참조 테이블의 데이터를 자동으로 가져와서 표시해요

{loadingEntityJoins ? ( -

로딩 중...

+
+
+ 조인 가능한 컬럼을 찾고 있어요... +
) : entityJoinData.joinTables.length === 0 ? ( -
-

+

+ +

{entityJoinTargetTable - ? `${entityJoinTargetTable} 테이블에 Entity 조인 가능한 컬럼이 없습니다` + ? "조인 가능한 컬럼이 없어요" : "저장 테이블을 먼저 설정해주세요"}

+ {entityJoinTargetTable && ( +

+ 테이블 타입 관리에서 엔티티 관계를 설정해보세요 +

+ )}
) : (
@@ -1873,8 +1929,8 @@ export const V2RepeaterConfigPanel: React.FC = ({ {/* 현재 설정된 Entity 조인 목록 */} {config.entityJoins && config.entityJoins.length > 0 && ( -
-

CONFIGURED JOINS

+
+ 설정된 조인 ({config.entityJoins.length})
{config.entityJoins.map((join, idx) => (