From a9d85b780bb82d51b622f4b1833622e4694f945e Mon Sep 17 00:00:00 2001 From: kjs Date: Mon, 27 Oct 2025 09:49:13 +0900 Subject: [PATCH] =?UTF-8?q?=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EA=B0=81=20?= =?UTF-8?q?=EB=8B=A8=EA=B3=84=EB=B3=84=20=EC=BB=AC=EB=9F=BC=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/controllers/flowController.ts | 2 + backend-node/src/services/flowStepService.ts | 27 +- backend-node/src/types/flow.ts | 14 + frontend/components/flow/FlowStepPanel.tsx | 167 +++++++ .../components/screen/widgets/FlowWidget.tsx | 60 ++- frontend/lib/api/tableManagement.ts | 4 +- frontend/next.config.mjs | 5 +- frontend/types/flow.ts | 14 + frontend/types/screen-management.ts | 12 + 플로우_위젯_컬럼_표시_설정_구현_완료.md | 414 ++++++++++++++++++ 10 files changed, 707 insertions(+), 12 deletions(-) create mode 100644 플로우_위젯_컬럼_표시_설정_구현_완료.md diff --git a/backend-node/src/controllers/flowController.ts b/backend-node/src/controllers/flowController.ts index f596af97..b13d6755 100644 --- a/backend-node/src/controllers/flowController.ts +++ b/backend-node/src/controllers/flowController.ts @@ -312,6 +312,7 @@ export class FlowController { fieldMappings, integrationType, integrationConfig, + displayConfig, } = req.body; const step = await this.flowStepService.update(id, { @@ -329,6 +330,7 @@ export class FlowController { fieldMappings, integrationType, integrationConfig, + displayConfig, }); if (!step) { diff --git a/backend-node/src/services/flowStepService.ts b/backend-node/src/services/flowStepService.ts index e8cf1fb9..ccb793eb 100644 --- a/backend-node/src/services/flowStepService.ts +++ b/backend-node/src/services/flowStepService.ts @@ -26,9 +26,9 @@ export class FlowStepService { flow_definition_id, step_name, step_order, table_name, condition_json, color, position_x, position_y, move_type, status_column, status_value, target_table, field_mappings, required_fields, - integration_type, integration_config + integration_type, integration_config, display_config ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) RETURNING * `; @@ -51,6 +51,7 @@ export class FlowStepService { request.integrationConfig ? JSON.stringify(request.integrationConfig) : null, + request.displayConfig ? JSON.stringify(request.displayConfig) : null, ]); return this.mapToFlowStep(result[0]); @@ -209,6 +210,15 @@ export class FlowStepService { paramIndex++; } + // 표시 설정 (displayConfig) + if (request.displayConfig !== undefined) { + fields.push(`display_config = $${paramIndex}`); + params.push( + request.displayConfig ? JSON.stringify(request.displayConfig) : null + ); + paramIndex++; + } + if (fields.length === 0) { return this.findById(id); } @@ -262,6 +272,17 @@ export class FlowStepService { * DB 행을 FlowStep 객체로 변환 */ private mapToFlowStep(row: any): FlowStep { + // JSONB 필드는 pg 라이브러리가 자동으로 파싱해줌 + const displayConfig = row.display_config; + + // 디버깅 로그 (개발 환경에서만) + if (displayConfig && process.env.NODE_ENV === "development") { + console.log(`🔍 [FlowStep ${row.id}] displayConfig:`, { + type: typeof displayConfig, + value: displayConfig, + }); + } + return { id: row.id, flowDefinitionId: row.flow_definition_id, @@ -282,6 +303,8 @@ export class FlowStepService { // 외부 연동 필드 integrationType: row.integration_type || "internal", integrationConfig: row.integration_config || undefined, + // 표시 설정 + displayConfig: displayConfig || undefined, createdAt: row.created_at, updatedAt: row.updated_at, }; diff --git a/backend-node/src/types/flow.ts b/backend-node/src/types/flow.ts index 4368ae1a..02510366 100644 --- a/backend-node/src/types/flow.ts +++ b/backend-node/src/types/flow.ts @@ -66,6 +66,14 @@ export interface FlowConditionGroup { conditions: FlowCondition[]; } +// 플로우 단계 표시 설정 +export interface FlowStepDisplayConfig { + visibleColumns?: string[]; // 표시할 컬럼 목록 + columnOrder?: string[]; // 컬럼 순서 (선택사항) + columnLabels?: Record; // 컬럼별 커스텀 라벨 (선택사항) + columnWidths?: Record; // 컬럼별 너비 설정 (px, 선택사항) +} + // 플로우 단계 export interface FlowStep { id: number; @@ -87,6 +95,8 @@ export interface FlowStep { // 외부 연동 필드 integrationType?: FlowIntegrationType; // 연동 타입 (기본값: internal) integrationConfig?: FlowIntegrationConfig; // 연동 설정 (JSONB) + // 🆕 표시 설정 (플로우 위젯에서 사용) + displayConfig?: FlowStepDisplayConfig; // 단계별 컬럼 표시 설정 createdAt: Date; updatedAt: Date; } @@ -111,6 +121,8 @@ export interface CreateFlowStepRequest { // 외부 연동 필드 integrationType?: FlowIntegrationType; integrationConfig?: FlowIntegrationConfig; + // 🆕 표시 설정 + displayConfig?: FlowStepDisplayConfig; } // 플로우 단계 수정 요청 @@ -132,6 +144,8 @@ export interface UpdateFlowStepRequest { // 외부 연동 필드 integrationType?: FlowIntegrationType; integrationConfig?: FlowIntegrationConfig; + // 🆕 표시 설정 + displayConfig?: FlowStepDisplayConfig; } // 플로우 단계 연결 diff --git a/frontend/components/flow/FlowStepPanel.tsx b/frontend/components/flow/FlowStepPanel.tsx index 8dc59a28..71b1be53 100644 --- a/frontend/components/flow/FlowStepPanel.tsx +++ b/frontend/components/flow/FlowStepPanel.tsx @@ -70,6 +70,8 @@ export function FlowStepPanel({ // 외부 연동 필드 integrationType: step.integrationType || "internal", integrationConfig: step.integrationConfig, + // 🆕 표시 설정 + displayConfig: step.displayConfig || { visibleColumns: [] }, }); const [tableList, setTableList] = useState([]); @@ -90,6 +92,10 @@ export function FlowStepPanel({ const [externalConnections, setExternalConnections] = useState([]); const [loadingConnections, setLoadingConnections] = useState(false); + // 🆕 표시 설정용 컬럼 목록 + const [availableColumns, setAvailableColumns] = useState([]); + const [loadingAvailableColumns, setLoadingAvailableColumns] = useState(false); + // 테이블 목록 조회 useEffect(() => { const loadTables = async () => { @@ -157,6 +163,73 @@ export function FlowStepPanel({ loadConnections(); }, []); + // 🆕 테이블이 선택되면 해당 테이블의 컬럼 목록 조회 (표시 설정용) + useEffect(() => { + const loadAvailableColumns = async () => { + const tableName = formData.tableName || flowTableName; + if (!tableName) { + setAvailableColumns([]); + return; + } + + try { + setLoadingAvailableColumns(true); + const response = await getTableColumns(tableName); + + console.log("🎨 [FlowStepPanel] 컬럼 목록 API 응답:", { + tableName, + success: response.success, + dataType: typeof response.data, + dataKeys: response.data ? Object.keys(response.data) : [], + isArray: Array.isArray(response.data), + message: response.message, + fullResponse: response, + }); + + if (response.success && response.data) { + // response.data가 객체일 경우 columns 배열 찾기 + let columnsArray: any[] = []; + + if (Array.isArray(response.data)) { + columnsArray = response.data; + } else if (response.data.columns && Array.isArray(response.data.columns)) { + columnsArray = response.data.columns; + } else if (response.data.data && Array.isArray(response.data.data)) { + columnsArray = response.data.data; + } else { + console.warn("⚠️ 예상치 못한 data 구조:", response.data); + } + + const columnNames = columnsArray.map((col: any) => col.columnName || col.column_name); + setAvailableColumns(columnNames); + + console.log("✅ [FlowStepPanel] 컬럼 목록 로드 성공:", { + tableName, + columns: columnNames, + }); + } else { + console.warn("⚠️ [FlowStepPanel] 컬럼 목록 조회 실패:", { + tableName, + message: response.message, + success: response.success, + hasData: !!response.data, + }); + setAvailableColumns([]); + } + } catch (error) { + console.error("❌ [FlowStepPanel] 컬럼 목록 로드 에러:", { + tableName, + error, + }); + setAvailableColumns([]); + } finally { + setLoadingAvailableColumns(false); + } + }; + + loadAvailableColumns(); + }, [formData.tableName, flowTableName]); + // 외부 DB 선택 시 해당 DB의 테이블 목록 조회 (JWT 토큰 사용) useEffect(() => { const loadExternalTables = async () => { @@ -250,6 +323,8 @@ export function FlowStepPanel({ // 외부 연동 필드 integrationType: step.integrationType || "internal", integrationConfig: step.integrationConfig, + // 표시 설정 (displayConfig 반드시 초기화) + displayConfig: step.displayConfig || { visibleColumns: [] }, }; console.log("✅ Setting formData:", newFormData); @@ -988,6 +1063,98 @@ export function FlowStepPanel({ + {/* 🆕 표시 설정 */} + + + 표시 설정 + 플로우 위젯에서 이 단계의 데이터를 표시할 때 보여줄 컬럼을 선택합니다 + + + {loadingAvailableColumns ? ( +
+ 컬럼 목록을 불러오는 중... +
+ ) : availableColumns.length === 0 ? ( +
+

+ ⚠️ 테이블의 컬럼 목록을 불러올 수 없습니다. +
+ 플로우 테이블:{" "} + {formData.tableName || flowTableName || "없음"} +
+ + 테이블이 존재하지 않거나, 컬럼이 없거나, 접근 권한이 없을 수 있습니다. + +

+
+ ) : ( + <> +
+ +

선택하지 않으면 모든 컬럼이 표시됩니다

+
+
+ { + setFormData({ + ...formData, + displayConfig: { + ...formData.displayConfig, + visibleColumns: e.target.checked ? [...availableColumns] : [], + }, + }); + }} + className="h-4 w-4" + /> + +
+
+ {availableColumns.map((colName) => ( +
+ { + const currentColumns = formData.displayConfig.visibleColumns || []; + const newColumns = e.target.checked + ? [...currentColumns, colName] + : currentColumns.filter((c) => c !== colName); + + setFormData({ + ...formData, + displayConfig: { + ...formData.displayConfig, + visibleColumns: newColumns, + }, + }); + }} + className="h-4 w-4" + /> + +
+ ))} +
+
+ +
+

+ 💡 선택된 컬럼: {formData.displayConfig.visibleColumns?.length || 0}개 + {formData.displayConfig.visibleColumns?.length === 0 && " (모든 컬럼이 표시됩니다)"} +

+
+ + )} + + + {/* 액션 버튼 */}