From 516517eb346b0595c32e7a58d30d2bc96bccd444 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Fri, 6 Mar 2026 14:06:53 +0900 Subject: [PATCH] =?UTF-8?q?feat(pop-button):=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=ED=8C=A8=EB=84=90=20UX/UI=20=EC=A0=84=EB=A9=B4=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20-=20=EB=B9=84=EA=B0=9C=EB=B0=9C=EC=9E=90=20?= =?UTF-8?q?=EC=B9=9C=ED=99=94=EC=A0=81=20=EC=84=A4=EC=A0=95=20=EA=B2=BD?= =?UTF-8?q?=ED=97=98=20=ED=99=94=EB=A9=B4=20=EB=94=94=EC=9E=90=EC=9D=B4?= =?UTF-8?q?=EB=84=88(=EB=B9=84=EA=B0=9C=EB=B0=9C=EC=9E=90)=EA=B0=80=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=9E=91=EC=97=85=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=EC=9D=84=20=EC=A7=81=EA=B4=80=EC=A0=81=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=ED=8C=A8=EB=84=90=EC=9D=98=20=EC=9A=A9=EC=96=B4,?= =?UTF-8?q?=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83,=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=EB=A5=BC=20=EC=A0=84=EB=A9=B4=20=EA=B0=9C=EC=84=A0=ED=95=9C?= =?UTF-8?q?=EB=8B=A4.=20[=EB=94=94=EC=9E=90=EC=9D=B8=20=ED=86=B5=EC=9D=BC]?= =?UTF-8?q?=20-=20Input/Select=20=EB=86=92=EC=9D=B4=20h-8,=20=EB=9D=BC?= =?UTF-8?q?=EB=B2=A8=20text-xs=20font-medium,=20=EB=8F=84=EC=9B=80?= =?UTF-8?q?=EB=A7=90=20text-[11px]=EB=A1=9C=20=ED=86=B5=EC=9D=BC=20-=20db-?= =?UTF-8?q?conditional=20UI=EB=A5=BC=20=EA=B0=80=EB=A1=9C=20=EB=82=98?= =?UTF-8?q?=EC=97=B4=EC=97=90=EC=84=9C=20=EC=84=B8=EB=A1=9C=20=EC=8A=A4?= =?UTF-8?q?=ED=83=9D=EC=9C=BC=EB=A1=9C=20=EC=A0=84=ED=99=98=20(=EC=A2=81?= =?UTF-8?q?=EC=9D=80=20=ED=8C=A8=EB=84=90=20=EC=9E=98=EB=A6=BC=20=EB=B0=A9?= =?UTF-8?q?=EC=A7=80)=20-=20=EC=9E=91=EC=97=85=20=ED=95=AD=EB=AA=A9=20?= =?UTF-8?q?=EA=B0=84=20=EA=B0=84=EA=B2=A9,=20=ED=8C=A8=EB=94=A9,=20?= =?UTF-8?q?=EB=91=A5=EA=B7=BC=20=EB=AA=A8=EC=84=9C=EB=A6=AC=20=EC=9D=BC?= =?UTF-8?q?=EA=B4=80=EC=84=B1=20=ED=99=95=EB=B3=B4=20[=EC=9E=90=EC=97=B0?= =?UTF-8?q?=EC=96=B4=20=EB=9D=BC=EB=B2=A8]=20-=20"=EB=8C=80=EC=83=81=20?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=B8=94"=20=E2=86=92=20"=EC=96=B4=EB=96=A4?= =?UTF-8?q?=20=ED=85=8C=EC=9D=B4=EB=B8=94=EC=9D=84=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=ED=95=A0=EA=B9=8C=EC=9A=94=3F"=20-=20"=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=BC"=20=E2=86=92=20"=EC=96=B4=EB=96=A4=20?= =?UTF-8?q?=ED=95=AD=EB=AA=A9(=EC=BB=AC=EB=9F=BC)=EC=9D=84=20=EB=B0=94?= =?UTF-8?q?=EA=BF=80=EA=B9=8C=EC=9A=94=3F"=20-=20"=EC=97=B0=EC=82=B0"=20?= =?UTF-8?q?=E2=86=92=20"=EC=96=B4=EB=96=BB=EA=B2=8C=20=EB=B0=94=EA=BF=80?= =?UTF-8?q?=EA=B9=8C=EC=9A=94=3F"=20+=20=EA=B0=81=20=EC=97=B0=EC=82=B0?= =?UTF-8?q?=EB=B3=84=20=EC=84=A4=EB=AA=85=20=EB=8F=84=EC=9B=80=EB=A7=90=20?= =?UTF-8?q?-=20"=EA=B0=92=20=EC=B6=9C=EC=B2=98:=20=EA=B3=A0=EC=A0=95?= =?UTF-8?q?=EA=B0=92"=20=E2=86=92=20"=EC=A7=81=EC=A0=91=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5",=20"=EC=97=B0=EA=B2=B0=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?"=20=E2=86=92=20"=ED=99=94=EB=A9=B4=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=EC=97=90=EC=84=9C=20=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EA=B8=B0"=20-=20=EB=B9=84=EA=B5=90=20=EC=97=B0=EC=82=B0?= =?UTF-8?q?=EC=9E=90=EC=97=90=20=ED=95=9C=EA=B8=80=20=EC=84=A4=EB=AA=85=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(">=3D"=20=E2=86=92=20">=3D=20(=EC=9D=B4?= =?UTF-8?q?=EC=83=81=EC=9D=B4=EB=A9=B4)")=20[=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0]=20-=20"=EC=A1=B0=ED=9A=8C=20=ED=82=A4"?= =?UTF-8?q?=EB=A5=BC=20"=EA=B3=A0=EA=B8=89=20=EC=84=A4=EC=A0=95"=20?= =?UTF-8?q?=ED=86=A0=EA=B8=80=EB=A1=9C=20=EC=88=A8=EA=B9=80=20(=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=EC=A0=91=ED=9E=98,=20=EB=8C=80=EB=B6=80=EB=B6=84?= =?UTF-8?q?=20=EC=9E=90=EB=8F=99=20=EB=A7=A4=EC=B9=AD)=20-=20"=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=20=ED=95=84=EB=93=9C=EB=AA=85"=20=EC=88=98=EB=8F=99?= =?UTF-8?q?=20=EC=9E=85=EB=A0=A5=20=E2=86=92=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=EC=97=90=EC=84=9C=20Select=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20-=20=EC=A0=91=ED=9E=8C=20=ED=97=A4=EB=8D=94?= =?UTF-8?q?=EC=97=90=20=EC=9A=94=EC=95=BD=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20+=20=EB=A7=88=EC=9A=B0=EC=8A=A4=20?= =?UTF-8?q?=ED=98=B8=EB=B2=84=20=EC=8B=9C=20=EC=A0=84=EC=B2=B4=20=ED=88=B4?= =?UTF-8?q?=ED=8C=81=20-=20=ED=8E=BC=EC=B9=9C=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=ED=95=98=EB=8B=A8=EC=97=90=20=EC=84=A4=EC=A0=95=20=EC=9A=94?= =?UTF-8?q?=EC=95=BD=20=EB=AF=B8=EB=A6=AC=EB=B3=B4=EA=B8=B0=20[=EC=BB=AC?= =?UTF-8?q?=EB=9F=BC=20=EC=BD=94=EB=A9=98=ED=8A=B8=20=ED=91=9C=EC=8B=9C]?= =?UTF-8?q?=20-=20=EB=B0=B1=EC=97=94=EB=93=9C:=20getTableSchema=20SQL?= =?UTF-8?q?=EC=97=90=20col=5Fdescription()=20=EC=B6=94=EA=B0=80=20-=20?= =?UTF-8?q?=ED=94=84=EB=A1=A0=ED=8A=B8:=20ColumnCombobox=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=EC=BD=94=EB=A9=98=ED=8A=B8=20=ED=91=9C=EC=8B=9C=20+=20?= =?UTF-8?q?=ED=95=9C=EA=B8=80=EB=AA=85=20=EA=B2=80=EC=83=89=20=EC=A7=80?= =?UTF-8?q?=EC=9B=90=20-=20ColumnInfo=20=EC=9D=B8=ED=84=B0=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=EC=97=90=20comment=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/services/tableManagementService.ts | 43 +- .../registry/pop-components/pop-button.tsx | 675 +++++++++++++----- .../pop-dashboard/utils/dataFetcher.ts | 2 + .../pop-shared/ColumnCombobox.tsx | 31 +- 4 files changed, 529 insertions(+), 222 deletions(-) diff --git a/backend-node/src/services/tableManagementService.ts b/backend-node/src/services/tableManagementService.ts index 791940ec..d6886a25 100644 --- a/backend-node/src/services/tableManagementService.ts +++ b/backend-node/src/services/tableManagementService.ts @@ -4446,26 +4446,30 @@ export class TableManagementService { const rawColumns = await query( `SELECT - column_name as "columnName", - column_name as "displayName", - data_type as "dataType", - udt_name as "dbType", - is_nullable as "isNullable", - column_default as "defaultValue", - character_maximum_length as "maxLength", - numeric_precision as "numericPrecision", - numeric_scale as "numericScale", + c.column_name as "columnName", + c.column_name as "displayName", + c.data_type as "dataType", + c.udt_name as "dbType", + c.is_nullable as "isNullable", + c.column_default as "defaultValue", + c.character_maximum_length as "maxLength", + c.numeric_precision as "numericPrecision", + c.numeric_scale as "numericScale", CASE - WHEN column_name IN ( - SELECT column_name FROM information_schema.key_column_usage - WHERE table_name = $1 AND constraint_name LIKE '%_pkey' + WHEN c.column_name IN ( + SELECT kcu.column_name FROM information_schema.key_column_usage kcu + WHERE kcu.table_name = $1 AND kcu.constraint_name LIKE '%_pkey' ) THEN true ELSE false - END as "isPrimaryKey" - FROM information_schema.columns - WHERE table_name = $1 - AND table_schema = 'public' - ORDER BY ordinal_position`, + END as "isPrimaryKey", + col_description( + (SELECT oid FROM pg_class WHERE relname = $1 AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')), + c.ordinal_position + ) as "columnComment" + FROM information_schema.columns c + WHERE c.table_name = $1 + AND c.table_schema = 'public' + ORDER BY c.ordinal_position`, [tableName] ); @@ -4475,10 +4479,10 @@ export class TableManagementService { displayName: col.displayName, dataType: col.dataType, dbType: col.dbType, - webType: "text", // 기본값 + webType: "text", inputType: "direct", detailSettings: "{}", - description: "", // 필수 필드 추가 + description: col.columnComment || "", isNullable: col.isNullable, isPrimaryKey: col.isPrimaryKey, defaultValue: col.defaultValue, @@ -4489,6 +4493,7 @@ export class TableManagementService { numericScale: col.numericScale ? Number(col.numericScale) : undefined, displayOrder: 0, isVisible: true, + columnComment: col.columnComment || "", })); logger.info( diff --git a/frontend/lib/registry/pop-components/pop-button.tsx b/frontend/lib/registry/pop-components/pop-button.tsx index ae6d05d9..56889eff 100644 --- a/frontend/lib/registry/pop-components/pop-button.tsx +++ b/frontend/lib/registry/pop-components/pop-button.tsx @@ -1267,11 +1267,51 @@ interface PopButtonConfigPanelProps { componentId?: string; } +/** 화면 내 카드 컴포넌트에서 사용 가능한 필드 목록 추출 */ +function extractCardFields( + allComponents?: PopButtonConfigPanelProps["allComponents"], +): { value: string; label: string; source: string }[] { + if (!allComponents) return []; + const fields: { value: string; label: string; source: string }[] = []; + + for (const comp of allComponents) { + if (comp.type !== "pop-card-list" || !comp.config) continue; + const tpl = (comp.config as Record).cardTemplate as + | { header?: Record; body?: { fields?: { id?: string; label?: string; valueType?: string; columnName?: string }[] } } + | undefined; + if (!tpl) continue; + + if (tpl.header?.codeField) { + fields.push({ value: String(tpl.header.codeField), label: String(tpl.header.codeField), source: "헤더 코드" }); + } + if (tpl.header?.titleField) { + fields.push({ value: String(tpl.header.titleField), label: String(tpl.header.titleField), source: "헤더 제목" }); + } + for (const f of tpl.body?.fields ?? []) { + if (f.valueType === "column" && f.columnName) { + fields.push({ value: f.columnName, label: f.label || f.columnName, source: "본문" }); + } else if (f.valueType === "formula" && f.label) { + const formulaKey = `__formula_${f.id || f.label}`; + fields.push({ value: formulaKey, label: f.label, source: "수식" }); + } + } + + // 시스템 필드 추가 + fields.push({ value: "__cart_quantity", label: "수량 (장바구니)", source: "시스템" }); + fields.push({ value: "__cart_row_key", label: "원본 키", source: "시스템" }); + fields.push({ value: "__cart_id", label: "카드 항목 ID", source: "시스템" }); + } + + return fields; +} + export function PopButtonConfigPanel({ config, onUpdate, + allComponents, }: PopButtonConfigPanelProps) { const v2 = useMemo(() => migrateButtonConfig(config), [config]); + const cardFields = useMemo(() => extractCardFields(allComponents), [allComponents]); const updateV2 = useCallback( (partial: Partial) => { @@ -1449,9 +1489,9 @@ export function PopButtonConfigPanel({ {/* 작업 목록 */} -
+
{v2.tasks.length === 0 && ( -

+

작업이 없습니다. 빠른 시작 또는 아래 버튼으로 추가하세요.

)} @@ -1465,6 +1505,7 @@ export function PopButtonConfigPanel({ onUpdate={(partial) => updateTask(task.id, partial)} onRemove={() => removeTask(task.id)} onMove={(dir) => moveTask(task.id, dir)} + cardFields={cardFields} /> ))} @@ -1490,6 +1531,41 @@ export function PopButtonConfigPanel({ // 작업 항목 에디터 (접힘/펼침) // ======================================== +/** 작업 항목의 요약 텍스트 생성 */ +function buildTaskSummary(task: ButtonTask): string { + switch (task.type) { + case "data-update": { + if (!task.targetTable) return ""; + const col = task.targetColumn ? `.${task.targetColumn}` : ""; + const opLabels: Record = { + assign: "값 지정", + add: "더하기", + subtract: "빼기", + multiply: "곱하기", + divide: "나누기", + conditional: "조건 분기", + "db-conditional": "조건 비교", + }; + const op = opLabels[task.operationType || "assign"] || ""; + return `${task.targetTable}${col} ${op}`; + } + case "data-delete": + return task.targetTable || ""; + case "navigate": + return task.targetScreenId ? `화면 ${task.targetScreenId}` : ""; + case "modal-open": + return task.modalTitle || task.modalScreenId || ""; + case "cart-save": + return task.cartScreenId ? `화면 ${task.cartScreenId}` : ""; + case "api-call": + return task.apiEndpoint || ""; + case "custom-event": + return task.eventName || ""; + default: + return ""; + } +} + function TaskItemEditor({ task, index, @@ -1497,6 +1573,7 @@ function TaskItemEditor({ onUpdate, onRemove, onMove, + cardFields, }: { task: ButtonTask; index: number; @@ -1504,55 +1581,61 @@ function TaskItemEditor({ onUpdate: (partial: Partial) => void; onRemove: () => void; onMove: (direction: "up" | "down") => void; + cardFields: { value: string; label: string; source: string }[]; }) { const [expanded, setExpanded] = useState(false); const designerCtx = usePopDesignerContext(); + const summary = buildTaskSummary(task); return ( -
- {/* 헤더: 타입 + 순서 + 삭제 */} +
setExpanded(!expanded)} > - - - {index + 1}. {TASK_TYPE_LABELS[task.type]} - - {task.label && ( - - ({task.label}) - - )} -
+
+
+ + {index + 1}. {TASK_TYPE_LABELS[task.type]} + + {summary && ( + + - {summary} + + )} +
+
+
{index > 0 && ( - )} {index < totalCount - 1 && ( - )}
- {/* 펼침: 타입별 설정 폼 */} {expanded && ( -
- +
+
)}
@@ -1567,10 +1650,12 @@ function TaskDetailForm({ task, onUpdate, designerCtx, + cardFields, }: { task: ButtonTask; onUpdate: (partial: Partial) => void; designerCtx: ReturnType; + cardFields: { value: string; label: string; source: string }[]; }) { // 테이블/컬럼 조회 (data-update, data-delete용) const [tables, setTables] = useState([]); @@ -1592,7 +1677,7 @@ function TaskDetailForm({ switch (task.type) { case "data-save": return ( -

+

연결된 입력 컴포넌트의 저장 매핑을 사용합니다. 별도 설정 불필요.

); @@ -1604,13 +1689,14 @@ function TaskDetailForm({ onUpdate={onUpdate} tables={tables} columns={columns} + cardFields={cardFields} /> ); case "data-delete": return ( -
- +
+ - +
+ onUpdate({ cartScreenId: e.target.value })} placeholder="비워두면 이동 없이 저장만" - className="h-7 text-xs" + className="h-8 text-xs" />
); case "modal-open": return ( -
-
- +
+
+
{task.modalMode === "screen-ref" && ( -
- +
+ onUpdate({ modalScreenId: e.target.value })} placeholder="화면 ID" - className="h-7 text-xs" + className="h-8 text-xs" />
)} -
- +
+ onUpdate({ modalTitle: e.target.value })} placeholder="모달 제목 (선택)" - className="h-7 text-xs" + className="h-8 text-xs" />
{task.modalMode === "fullscreen" && designerCtx && (
{task.modalScreenId ? ( - ) : ( -
-
- 이면 -> - updateCondition(cIdx, { thenValue: e.target.value })} className="h-7 text-[10px]" placeholder="변경할 값" /> -
+ ) : ( + onUpdate({ sourceField: e.target.value })} + className="h-8 text-xs" + placeholder="필드명 직접 입력 (예: qty)" + /> + )} +

+ {cardFields.length > 0 + ? "카드에 표시되는 데이터 중 하나를 선택합니다" + : "카드 컴포넌트가 없으면 직접 입력해주세요"} +

- ))} - -
- 그 외 -> + )} +
+ )} + + {/* 5. 조건 비교 (db-conditional) - 세로 스택 */} + {task.operationType === "db-conditional" && ( +
+

+ DB 컬럼 값을 비교해서 결과를 정합니다 +

+ +
+ + onUpdate({ compareColumn: v })} + placeholder="비교할 컬럼 선택" + /> +
+ +
+ + +
+ +
+ + onUpdate({ compareWith: v })} + placeholder="비교 대상 컬럼 선택" + /> +
+ +
+ onUpdate({ conditionalValue: { conditions, defaultValue: e.target.value } })} - className="h-7 text-[10px]" - placeholder="기본값" + value={task.dbThenValue ?? ""} + onChange={(e) => onUpdate({ dbThenValue: e.target.value })} + className="h-8 text-xs" + placeholder="예: 입고완료" + /> +
+ +
+ + onUpdate({ dbElseValue: e.target.value })} + className="h-8 text-xs" + placeholder="예: 부분입고" />
)} - {/* 조회 키 */} -
-
- - + {/* 6. 조건 분기 (conditional) */} + {task.operationType === "conditional" && ( +
+

+ 입력된 값에 따라 다른 결과를 지정합니다 +

+ + {conditions.map((cond, cIdx) => ( +
+
+ 조건 {cIdx + 1} + +
+
+ + updateCondition(cIdx, { whenColumn: v })} + placeholder="컬럼 선택" + /> +
+
+ +
+ + updateCondition(cIdx, { whenValue: e.target.value })} + className="h-8 flex-1 text-xs" + placeholder="비교할 값" + /> +
+
+
+ + updateCondition(cIdx, { thenValue: e.target.value })} + className="h-8 text-xs" + placeholder="변경할 값" + /> +
+
+ ))} + + + +
+ + onUpdate({ conditionalValue: { conditions, defaultValue: e.target.value } })} + className="h-8 text-xs" + placeholder="기본값 입력" + /> +
- {task.lookupMode === "manual" && ( -
- - -> - onUpdate({ manualPkColumn: v })} placeholder="대상 PK 컬럼" /> + )} + + {/* 7. 고급 설정 (조회 키) */} +
+ + {showAdvanced && ( +
+
+ + +

+ {task.lookupMode === "manual" + ? "카드 항목의 필드를 직접 지정하여 대상 행을 찾습니다" + : "카드 항목과 테이블 PK를 자동으로 매칭합니다"} +

+
+ {task.lookupMode === "manual" && ( +
+
+ + +
+
+ + onUpdate({ manualPkColumn: v })} + placeholder="PK 컬럼 선택" + /> +
+
+ )}
)}
+ + {/* 8. 설정 요약 */} + {summaryText && ( +
+

설정 요약

+

{summaryText}

+
+ )} )}
@@ -2326,10 +2605,10 @@ function PopButtonPreviewComponent({ // ======================================== const KNOWN_ITEM_FIELDS = [ - { value: "__cart_id", label: "__cart_id (카드 항목 ID)" }, - { value: "__cart_row_key", label: "__cart_row_key (원본 PK 값)" }, - { value: "id", label: "id" }, - { value: "row_key", label: "row_key" }, + { value: "__cart_row_key", label: "카드 항목의 원본 키", desc: "DB에서 가져온 데이터의 PK (가장 일반적)" }, + { value: "__cart_id", label: "카드 항목 ID", desc: "장바구니 내부 고유 ID" }, + { value: "id", label: "id", desc: "데이터의 id 컬럼" }, + { value: "row_key", label: "row_key", desc: "데이터의 row_key 컬럼" }, ]; function StatusChangeRuleEditor({ diff --git a/frontend/lib/registry/pop-components/pop-dashboard/utils/dataFetcher.ts b/frontend/lib/registry/pop-components/pop-dashboard/utils/dataFetcher.ts index 0f6adda6..b05846ef 100644 --- a/frontend/lib/registry/pop-components/pop-dashboard/utils/dataFetcher.ts +++ b/frontend/lib/registry/pop-components/pop-dashboard/utils/dataFetcher.ts @@ -34,6 +34,7 @@ export interface ColumnInfo { type: string; udtName: string; isPrimaryKey?: boolean; + comment?: string; } // ===== SQL 값 이스케이프 ===== @@ -330,6 +331,7 @@ export async function fetchTableColumns( type: col.dataType || col.data_type || col.type || "unknown", udtName: col.dbType || col.udt_name || col.udtName || "unknown", isPrimaryKey: col.isPrimaryKey === true || col.isPrimaryKey === "true" || col.is_primary_key === true || col.is_primary_key === "true", + comment: col.columnComment || col.description || "", })); } } diff --git a/frontend/lib/registry/pop-components/pop-shared/ColumnCombobox.tsx b/frontend/lib/registry/pop-components/pop-shared/ColumnCombobox.tsx index 62d63f02..99444d95 100644 --- a/frontend/lib/registry/pop-components/pop-shared/ColumnCombobox.tsx +++ b/frontend/lib/registry/pop-components/pop-shared/ColumnCombobox.tsx @@ -38,9 +38,23 @@ export function ColumnCombobox({ const filtered = useMemo(() => { if (!search) return columns; const q = search.toLowerCase(); - return columns.filter((c) => c.name.toLowerCase().includes(q)); + return columns.filter( + (c) => + c.name.toLowerCase().includes(q) || + (c.comment && c.comment.toLowerCase().includes(q)) + ); }, [columns, search]); + const selectedCol = useMemo( + () => columns.find((c) => c.name === value), + [columns, value], + ); + const displayValue = selectedCol + ? selectedCol.comment + ? `${selectedCol.name} (${selectedCol.comment})` + : selectedCol.name + : ""; + return ( @@ -50,7 +64,7 @@ export function ColumnCombobox({ aria-expanded={open} className="mt-1 h-8 w-full justify-between text-xs" > - {value || placeholder} + {displayValue || placeholder} @@ -61,7 +75,7 @@ export function ColumnCombobox({ > -
- {col.name} +
+
+ {col.name} + {col.comment && ( + + ({col.comment}) + + )} +
{col.type}