플로우 각 단계별 컬럼 설정기능
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -66,6 +66,14 @@ export interface FlowConditionGroup {
|
||||
conditions: FlowCondition[];
|
||||
}
|
||||
|
||||
// 플로우 단계 표시 설정
|
||||
export interface FlowStepDisplayConfig {
|
||||
visibleColumns?: string[]; // 표시할 컬럼 목록
|
||||
columnOrder?: string[]; // 컬럼 순서 (선택사항)
|
||||
columnLabels?: Record<string, string>; // 컬럼별 커스텀 라벨 (선택사항)
|
||||
columnWidths?: Record<string, number>; // 컬럼별 너비 설정 (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;
|
||||
}
|
||||
|
||||
// 플로우 단계 연결
|
||||
|
||||
@@ -70,6 +70,8 @@ export function FlowStepPanel({
|
||||
// 외부 연동 필드
|
||||
integrationType: step.integrationType || "internal",
|
||||
integrationConfig: step.integrationConfig,
|
||||
// 🆕 표시 설정
|
||||
displayConfig: step.displayConfig || { visibleColumns: [] },
|
||||
});
|
||||
|
||||
const [tableList, setTableList] = useState<any[]>([]);
|
||||
@@ -90,6 +92,10 @@ export function FlowStepPanel({
|
||||
const [externalConnections, setExternalConnections] = useState<FlowExternalDbConnection[]>([]);
|
||||
const [loadingConnections, setLoadingConnections] = useState(false);
|
||||
|
||||
// 🆕 표시 설정용 컬럼 목록
|
||||
const [availableColumns, setAvailableColumns] = useState<string[]>([]);
|
||||
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({
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 🆕 표시 설정 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>표시 설정</CardTitle>
|
||||
<CardDescription>플로우 위젯에서 이 단계의 데이터를 표시할 때 보여줄 컬럼을 선택합니다</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{loadingAvailableColumns ? (
|
||||
<div className="text-muted-foreground flex items-center justify-center py-8 text-sm">
|
||||
컬럼 목록을 불러오는 중...
|
||||
</div>
|
||||
) : availableColumns.length === 0 ? (
|
||||
<div className="rounded-md bg-yellow-50 p-4 text-center">
|
||||
<p className="text-sm text-yellow-900">
|
||||
⚠️ 테이블의 컬럼 목록을 불러올 수 없습니다.
|
||||
<br />
|
||||
플로우 테이블:{" "}
|
||||
<span className="font-mono font-semibold">{formData.tableName || flowTableName || "없음"}</span>
|
||||
<br />
|
||||
<span className="text-xs">
|
||||
테이블이 존재하지 않거나, 컬럼이 없거나, 접근 권한이 없을 수 있습니다.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div>
|
||||
<Label>표시할 컬럼 선택</Label>
|
||||
<p className="text-muted-foreground mb-2 text-xs">선택하지 않으면 모든 컬럼이 표시됩니다</p>
|
||||
<div className="max-h-64 space-y-2 overflow-y-auto rounded-md border p-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="select-all-columns"
|
||||
checked={formData.displayConfig.visibleColumns?.length === availableColumns.length}
|
||||
onChange={(e) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
displayConfig: {
|
||||
...formData.displayConfig,
|
||||
visibleColumns: e.target.checked ? [...availableColumns] : [],
|
||||
},
|
||||
});
|
||||
}}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
<label htmlFor="select-all-columns" className="text-sm font-medium">
|
||||
전체 선택/해제
|
||||
</label>
|
||||
</div>
|
||||
<div className="border-t pt-2" />
|
||||
{availableColumns.map((colName) => (
|
||||
<div key={colName} className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`col-${colName}`}
|
||||
checked={formData.displayConfig.visibleColumns?.includes(colName) || false}
|
||||
onChange={(e) => {
|
||||
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"
|
||||
/>
|
||||
<label htmlFor={`col-${colName}`} className="cursor-pointer font-mono text-sm">
|
||||
{colName}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-md bg-blue-50 p-3">
|
||||
<p className="text-sm text-blue-900">
|
||||
💡 선택된 컬럼: {formData.displayConfig.visibleColumns?.length || 0}개
|
||||
{formData.displayConfig.visibleColumns?.length === 0 && " (모든 컬럼이 표시됩니다)"}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 액션 버튼 */}
|
||||
<div className="flex gap-2">
|
||||
<Button className="flex-1" onClick={handleSave}>
|
||||
|
||||
@@ -69,6 +69,40 @@ export function FlowWidget({
|
||||
const [stepDataLoading, setStepDataLoading] = useState(false);
|
||||
const [selectedRows, setSelectedRows] = useState<Set<number>>(new Set());
|
||||
|
||||
/**
|
||||
* 🆕 컬럼 표시 결정 함수
|
||||
* 1순위: 플로우 스텝 기본 설정 (displayConfig)
|
||||
* 2순위: 모든 컬럼 표시
|
||||
*/
|
||||
const getVisibleColumns = (stepId: number, allColumns: string[], stepsArray?: FlowStep[]): string[] => {
|
||||
// stepsArray가 제공되지 않으면 state의 steps 사용
|
||||
const effectiveSteps = stepsArray || steps;
|
||||
|
||||
// 1순위: 플로우 스텝 기본 설정
|
||||
console.log(`🔍 [FlowWidget] steps 배열 상태:`, {
|
||||
stepsLength: effectiveSteps.length,
|
||||
stepsIds: effectiveSteps.map((s) => s.id),
|
||||
targetStepId: stepId,
|
||||
});
|
||||
|
||||
const currentStep = effectiveSteps.find((s) => s.id === stepId);
|
||||
console.log(`🔍 [FlowWidget] currentStep 찾기 (스텝 ${stepId}):`, {
|
||||
found: !!currentStep,
|
||||
hasDisplayConfig: !!currentStep?.displayConfig,
|
||||
displayConfig: currentStep?.displayConfig,
|
||||
displayConfigType: typeof currentStep?.displayConfig,
|
||||
});
|
||||
|
||||
if (currentStep?.displayConfig?.visibleColumns && currentStep.displayConfig.visibleColumns.length > 0) {
|
||||
console.log(`🎨 [FlowWidget] 플로우 기본 설정 적용 (스텝 ${stepId}):`, currentStep.displayConfig.visibleColumns);
|
||||
return currentStep.displayConfig.visibleColumns;
|
||||
}
|
||||
|
||||
// 2순위: 모든 컬럼 표시
|
||||
console.log(`🎨 [FlowWidget] 전체 컬럼 표시 (스텝 ${stepId}):`, allColumns);
|
||||
return allColumns;
|
||||
};
|
||||
|
||||
// 🆕 스텝 데이터 페이지네이션 상태
|
||||
const [stepDataPage, setStepDataPage] = useState(1);
|
||||
const [stepDataPageSize, setStepDataPageSize] = useState(10);
|
||||
@@ -128,9 +162,11 @@ export function FlowWidget({
|
||||
const rows = response.data?.records || [];
|
||||
setStepData(rows);
|
||||
|
||||
// 컬럼 추출
|
||||
// 🆕 컬럼 추출 및 우선순위 적용
|
||||
if (rows.length > 0) {
|
||||
setStepDataColumns(Object.keys(rows[0]));
|
||||
const allColumns = Object.keys(rows[0]);
|
||||
const visibleColumns = getVisibleColumns(selectedStepId, allColumns);
|
||||
setStepDataColumns(visibleColumns);
|
||||
} else {
|
||||
setStepDataColumns([]);
|
||||
}
|
||||
@@ -175,6 +211,15 @@ export function FlowWidget({
|
||||
}
|
||||
if (stepsResponse.data) {
|
||||
const sortedSteps = stepsResponse.data.sort((a: FlowStep, b: FlowStep) => a.stepOrder - b.stepOrder);
|
||||
console.log("📋 [FlowWidget] 스텝 목록 로드:", {
|
||||
stepsCount: sortedSteps.length,
|
||||
steps: sortedSteps.map((s: FlowStep) => ({
|
||||
id: s.id,
|
||||
name: s.stepName,
|
||||
hasDisplayConfig: !!s.displayConfig,
|
||||
displayConfig: s.displayConfig,
|
||||
})),
|
||||
});
|
||||
setSteps(sortedSteps);
|
||||
|
||||
// 연결 정보 조회
|
||||
@@ -214,7 +259,10 @@ export function FlowWidget({
|
||||
const rows = response.data?.records || [];
|
||||
setStepData(rows);
|
||||
if (rows.length > 0) {
|
||||
setStepDataColumns(Object.keys(rows[0]));
|
||||
const allColumns = Object.keys(rows[0]);
|
||||
// sortedSteps를 직접 전달하여 타이밍 이슈 해결
|
||||
const visibleColumns = getVisibleColumns(firstStep.id, allColumns, sortedSteps);
|
||||
setStepDataColumns(visibleColumns);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -290,9 +338,11 @@ export function FlowWidget({
|
||||
const rows = response.data?.records || [];
|
||||
setStepData(rows);
|
||||
|
||||
// 컬럼 추출
|
||||
// 🆕 컬럼 추출 및 우선순위 적용
|
||||
if (rows.length > 0) {
|
||||
setStepDataColumns(Object.keys(rows[0]));
|
||||
const allColumns = Object.keys(rows[0]);
|
||||
const visibleColumns = getVisibleColumns(stepId, allColumns);
|
||||
setStepDataColumns(visibleColumns);
|
||||
} else {
|
||||
setStepDataColumns([]);
|
||||
}
|
||||
|
||||
@@ -85,9 +85,9 @@ class TableManagementApi {
|
||||
/**
|
||||
* 특정 테이블의 컬럼 목록 조회
|
||||
*/
|
||||
async getColumnList(tableName: string): Promise<ColumnListResponse> {
|
||||
async getColumnList(tableName: string, size: number = 1000): Promise<ColumnListResponse> {
|
||||
try {
|
||||
const response = await apiClient.get(`${this.basePath}/tables/${tableName}/columns`);
|
||||
const response = await apiClient.get(`${this.basePath}/tables/${tableName}/columns?size=${size}`);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
console.error(`❌ 테이블 '${tableName}' 컬럼 목록 조회 실패:`, error);
|
||||
|
||||
@@ -44,9 +44,8 @@ const nextConfig = {
|
||||
|
||||
// 환경 변수 (런타임에 읽기)
|
||||
env: {
|
||||
// 환경변수가 있으면 사용, 없으면 개발환경에서는 프록시 사용
|
||||
NEXT_PUBLIC_API_URL:
|
||||
process.env.NEXT_PUBLIC_API_URL || (process.env.NODE_ENV === "production" ? "http://localhost:8080/api" : "/api"),
|
||||
// 항상 명시적으로 백엔드 포트(8080)를 지정
|
||||
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080/api",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -61,6 +61,16 @@ export interface UpdateFlowDefinitionRequest {
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 플로우 단계 표시 설정
|
||||
// ============================================
|
||||
export interface FlowStepDisplayConfig {
|
||||
visibleColumns?: string[]; // 표시할 컬럼 목록
|
||||
columnOrder?: string[]; // 컬럼 순서 (선택사항)
|
||||
columnLabels?: Record<string, string>; // 컬럼별 커스텀 라벨 (선택사항)
|
||||
columnWidths?: Record<string, number>; // 컬럼별 너비 설정 (px, 선택사항)
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 플로우 단계
|
||||
// ============================================
|
||||
@@ -74,6 +84,8 @@ export interface FlowStep {
|
||||
color: string;
|
||||
positionX: number;
|
||||
positionY: number;
|
||||
// 🆕 표시 설정 (플로우 위젯에서 사용)
|
||||
displayConfig?: FlowStepDisplayConfig; // 단계별 컬럼 표시 설정
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
createdBy: string;
|
||||
@@ -88,6 +100,7 @@ export interface CreateFlowStepRequest {
|
||||
color?: string;
|
||||
positionX?: number;
|
||||
positionY?: number;
|
||||
displayConfig?: FlowStepDisplayConfig; // 🆕 표시 설정
|
||||
}
|
||||
|
||||
export interface UpdateFlowStepRequest {
|
||||
@@ -98,6 +111,7 @@ export interface UpdateFlowStepRequest {
|
||||
color?: string;
|
||||
positionX?: number;
|
||||
positionY?: number;
|
||||
displayConfig?: FlowStepDisplayConfig; // 🆕 표시 설정
|
||||
}
|
||||
|
||||
// ============================================
|
||||
|
||||
@@ -135,6 +135,14 @@ export interface FileComponent extends BaseComponent {
|
||||
lastFileUpdate?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 플로우 스텝별 컬럼 표시 설정
|
||||
*/
|
||||
export interface FlowStepColumnConfig {
|
||||
selectedColumns: string[]; // 표시할 컬럼 목록
|
||||
columnOrder?: string[]; // 컬럼 순서 (선택사항)
|
||||
}
|
||||
|
||||
/**
|
||||
* 플로우 컴포넌트
|
||||
*/
|
||||
@@ -145,6 +153,10 @@ export interface FlowComponent extends BaseComponent {
|
||||
showStepCount?: boolean; // 각 스텝의 데이터 건수 표시 여부
|
||||
allowDataMove?: boolean; // 데이터 이동 허용 여부
|
||||
displayMode?: "horizontal" | "vertical"; // 플로우 표시 방향
|
||||
// 🆕 단계별 컬럼 오버라이드 설정 (화면관리에서 설정)
|
||||
stepColumnConfig?: {
|
||||
[stepId: number]: FlowStepColumnConfig;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
414
플로우_위젯_컬럼_표시_설정_구현_완료.md
Normal file
414
플로우_위젯_컬럼_표시_설정_구현_완료.md
Normal file
@@ -0,0 +1,414 @@
|
||||
# 플로우 위젯 단계별 컬럼 표시 설정 기능 구현 완료
|
||||
|
||||
## 📋 개요
|
||||
|
||||
플로우 위젯에서 각 단계별로 표시할 컬럼을 선택할 수 있는 기능을 **하이브리드 방식**으로 구현하였습니다.
|
||||
|
||||
### 하이브리드 방식의 장점
|
||||
|
||||
1. **플로우 에디터**에서 각 스텝의 **기본 컬럼 설정** 정의
|
||||
2. **화면관리**에서 특정 화면에만 **컬럼 오버라이드** 가능
|
||||
3. **우선순위**: 화면 설정 > 플로우 기본 설정 > 전체 컬럼 표시
|
||||
|
||||
---
|
||||
|
||||
## 🎯 구현 내용
|
||||
|
||||
### Phase 1: 데이터베이스 스키마 확장 ✅
|
||||
|
||||
**파일**: `db/migrations/023_add_display_config_to_flow_step.sql`
|
||||
|
||||
- `flow_step` 테이블에 `display_config` 컬럼 추가 (JSONB 타입)
|
||||
- 인덱스 추가 (GIN 인덱스로 JSONB 검색 최적화)
|
||||
|
||||
```sql
|
||||
ALTER TABLE flow_step
|
||||
ADD COLUMN IF NOT EXISTS display_config JSONB DEFAULT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_flow_step_display_config
|
||||
ON flow_step USING gin(display_config);
|
||||
```
|
||||
|
||||
**구조**:
|
||||
|
||||
```json
|
||||
{
|
||||
"visibleColumns": ["column1", "column2", ...],
|
||||
"columnOrder": ["column1", "column2", ...],
|
||||
"columnLabels": {
|
||||
"column1": "커스텀 라벨 1"
|
||||
},
|
||||
"columnWidths": {
|
||||
"column1": 150
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: 백엔드 타입 정의 업데이트 ✅
|
||||
|
||||
**파일**: `backend-node/src/types/flow.ts`
|
||||
|
||||
**새로운 인터페이스**:
|
||||
|
||||
```typescript
|
||||
export interface FlowStepDisplayConfig {
|
||||
visibleColumns?: string[];
|
||||
columnOrder?: string[];
|
||||
columnLabels?: Record<string, string>;
|
||||
columnWidths?: Record<string, number>;
|
||||
}
|
||||
```
|
||||
|
||||
**FlowStep 인터페이스 확장**:
|
||||
|
||||
```typescript
|
||||
export interface FlowStep {
|
||||
// ... 기존 필드
|
||||
displayConfig?: FlowStepDisplayConfig; // 🆕
|
||||
}
|
||||
```
|
||||
|
||||
**CreateFlowStepRequest 및 UpdateFlowStepRequest에도 추가됨**
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: 워크플로우 에디터 - 스텝 속성 패널 UI 추가 ✅
|
||||
|
||||
**파일**: `frontend/components/flow/FlowStepPanel.tsx`
|
||||
|
||||
#### 추가된 기능
|
||||
|
||||
1. **테이블 컬럼 목록 자동 로드**
|
||||
- 스텝의 테이블이 선택되면 해당 테이블의 컬럼 목록을 자동으로 가져옴
|
||||
2. **"표시 설정" 카드 추가**
|
||||
- 전체 선택/해제 체크박스
|
||||
- 개별 컬럼 선택 체크박스
|
||||
- 선택된 컬럼 개수 표시
|
||||
|
||||
```typescript
|
||||
// formData에 displayConfig 추가
|
||||
const [formData, setFormData] = useState({
|
||||
// ... 기존 필드
|
||||
displayConfig: step.displayConfig || { visibleColumns: [] }, // 🆕
|
||||
});
|
||||
```
|
||||
|
||||
#### UI 구조
|
||||
|
||||
```
|
||||
┌─ 표시 설정 카드 ─────────────────────┐
|
||||
│ 표시할 컬럼 선택 │
|
||||
│ ┌────────────────────────────┐ │
|
||||
│ │ ☐ 전체 선택/해제 │ │
|
||||
│ ├────────────────────────────┤ │
|
||||
│ │ ☑ id │ │
|
||||
│ │ ☑ name │ │
|
||||
│ │ ☐ description │ │
|
||||
│ │ ☑ created_at │ │
|
||||
│ └────────────────────────────┘ │
|
||||
│ │
|
||||
│ 💡 선택된 컬럼: 3개 │
|
||||
└───────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: 플로우 위젯 - 컬럼 표시 로직 업데이트 ✅
|
||||
|
||||
**파일**: `frontend/components/screen/widgets/FlowWidget.tsx`
|
||||
|
||||
#### 핵심 로직: getVisibleColumns 함수
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 컬럼 표시 우선순위 결정 함수 (하이브리드 방식)
|
||||
* 1순위: 화면 위젯 설정 (stepColumnConfig)
|
||||
* 2순위: 플로우 스텝 기본 설정 (displayConfig)
|
||||
* 3순위: 모든 컬럼 표시
|
||||
*/
|
||||
const getVisibleColumns = (stepId: number, allColumns: string[]): string[] => {
|
||||
// 1순위: 화면 위젯 설정
|
||||
const widgetConfig = component.stepColumnConfig;
|
||||
if (widgetConfig && widgetConfig[stepId]) {
|
||||
const config = widgetConfig[stepId];
|
||||
if (config.selectedColumns && config.selectedColumns.length > 0) {
|
||||
return config.selectedColumns;
|
||||
}
|
||||
}
|
||||
|
||||
// 2순위: 플로우 스텝 기본 설정
|
||||
const currentStep = steps.find((s) => s.id === stepId);
|
||||
if (
|
||||
currentStep?.displayConfig?.visibleColumns &&
|
||||
currentStep.displayConfig.visibleColumns.length > 0
|
||||
) {
|
||||
return currentStep.displayConfig.visibleColumns;
|
||||
}
|
||||
|
||||
// 3순위: 모든 컬럼 표시
|
||||
return allColumns;
|
||||
};
|
||||
```
|
||||
|
||||
#### 적용된 위치
|
||||
|
||||
1. 스텝 클릭 시 데이터 로드 (`handleStepClick`)
|
||||
2. 플로우 새로고침 시 (`refreshStepData`)
|
||||
|
||||
---
|
||||
|
||||
### Phase 5: 화면관리 - 플로우 위젯 설정 패널 UI 추가 ✅
|
||||
|
||||
**파일**: `frontend/components/screen/config-panels/FlowWidgetConfigPanel.tsx`
|
||||
|
||||
#### 추가된 기능
|
||||
|
||||
1. **스텝 목록 자동 로드**
|
||||
|
||||
- 플로우가 선택되면 해당 플로우의 스텝 목록을 자동으로 가져옴
|
||||
|
||||
2. **테이블 컬럼 목록 자동 로드**
|
||||
|
||||
- 플로우의 테이블 컬럼 목록을 자동으로 가져옴
|
||||
|
||||
3. **"스텝별 컬럼 설정 (고급)" 섹션 추가**
|
||||
- 토글 버튼으로 표시/숨김 가능
|
||||
- 각 스텝별로 컬럼 선택 가능
|
||||
- 커스텀 설정이 있으면 "초기화" 버튼 표시
|
||||
|
||||
#### UI 구조
|
||||
|
||||
```
|
||||
┌─ 스텝별 컬럼 설정 (고급) ──────── [설정▼] ─┐
|
||||
│ │
|
||||
│ ┌─ 스텝 1: 주문 접수 ─────────── [초기화] ─┐ │
|
||||
│ │ 커스텀 설정 (3개 컬럼) │ │
|
||||
│ │ ┌──────────────────────────┐ │ │
|
||||
│ │ │ ☑ id │ │ │
|
||||
│ │ │ ☑ customer_name │ │ │
|
||||
│ │ │ ☐ order_date │ │ │
|
||||
│ │ │ ☑ amount │ │ │
|
||||
│ │ └──────────────────────────┘ │ │
|
||||
│ └─────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─ 스텝 2: 승인 대기 ──────────────────────┐ │
|
||||
│ │ 기본 설정 사용 │ │
|
||||
│ │ (플로우 정의에서 설정된 컬럼 표시) │ │
|
||||
│ └─────────────────────────────────────────┘ │
|
||||
└───────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 6: 프론트엔드 타입 정의 업데이트 ✅
|
||||
|
||||
**파일 1**: `frontend/types/flow.ts`
|
||||
|
||||
```typescript
|
||||
export interface FlowStepDisplayConfig {
|
||||
visibleColumns?: string[];
|
||||
columnOrder?: string[];
|
||||
columnLabels?: Record<string, string>;
|
||||
columnWidths?: Record<string, number>;
|
||||
}
|
||||
|
||||
export interface FlowStep {
|
||||
// ... 기존 필드
|
||||
displayConfig?: FlowStepDisplayConfig; // 🆕
|
||||
}
|
||||
```
|
||||
|
||||
**파일 2**: `frontend/types/screen-management.ts`
|
||||
|
||||
```typescript
|
||||
export interface FlowStepColumnConfig {
|
||||
selectedColumns: string[];
|
||||
columnOrder?: string[];
|
||||
}
|
||||
|
||||
export interface FlowComponent extends BaseComponent {
|
||||
type: "flow";
|
||||
// ... 기존 필드
|
||||
stepColumnConfig?: {
|
||||
[stepId: number]: FlowStepColumnConfig; // 🆕
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 사용 시나리오
|
||||
|
||||
### 시나리오 1: 플로우 기본 설정 사용 (일반적인 경우)
|
||||
|
||||
1. 플로우 관리 페이지에서 플로우 스텝 편집
|
||||
2. "표시 설정" 카드에서 표시할 컬럼 선택 (예: id, name, created_at)
|
||||
3. 저장
|
||||
4. **모든 화면**에서 이 플로우를 사용할 때 선택한 컬럼만 표시됨
|
||||
|
||||
### 시나리오 2: 화면별 컬럼 오버라이드 (특수한 경우)
|
||||
|
||||
1. 화면 설계자 모드에서 플로우 위젯 선택
|
||||
2. 속성 패널 > "스텝별 컬럼 설정 (고급)" 클릭
|
||||
3. 특정 스텝의 컬럼을 다르게 선택 (예: id, amount만 표시)
|
||||
4. 저장
|
||||
5. **이 화면에서만** 오버라이드된 컬럼이 표시됨
|
||||
|
||||
### 시나리오 3: 아무 설정도 하지 않은 경우
|
||||
|
||||
- 모든 컬럼이 자동으로 표시됨 (기본 동작)
|
||||
|
||||
---
|
||||
|
||||
## 📊 우선순위 다이어그램
|
||||
|
||||
```
|
||||
데이터 로드
|
||||
↓
|
||||
┌────────────────────────────────────────┐
|
||||
│ 1. 화면 위젯 설정 확인 │
|
||||
│ (stepColumnConfig[stepId]) │
|
||||
└────────────────────────────────────────┘
|
||||
↓ 없음
|
||||
┌────────────────────────────────────────┐
|
||||
│ 2. 플로우 스텝 기본 설정 확인 │
|
||||
│ (step.displayConfig.visibleColumns)│
|
||||
└────────────────────────────────────────┘
|
||||
↓ 없음
|
||||
┌────────────────────────────────────────┐
|
||||
│ 3. 모든 컬럼 표시 │
|
||||
│ (Object.keys(data[0])) │
|
||||
└────────────────────────────────────────┘
|
||||
↓
|
||||
테이블 렌더링
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 UI/UX 특징
|
||||
|
||||
### 1. 점진적 공개 (Progressive Disclosure)
|
||||
|
||||
- 기본적으로는 간단한 UI만 표시
|
||||
- "스텝별 컬럼 설정 (고급)"은 토글로 숨김/표시
|
||||
|
||||
### 2. 명확한 피드백
|
||||
|
||||
- 선택된 컬럼 개수 표시
|
||||
- "커스텀 설정" vs "기본 설정 사용" 명확히 표시
|
||||
- 초기화 버튼으로 쉽게 되돌리기 가능
|
||||
|
||||
### 3. 검색 가능한 체크박스 리스트
|
||||
|
||||
- 컬럼이 많을 경우 스크롤 가능
|
||||
- font-mono로 컬럼명 명확히 표시
|
||||
|
||||
---
|
||||
|
||||
## 🧪 테스트 체크리스트
|
||||
|
||||
### 기능 테스트
|
||||
|
||||
- [ ] 데이터베이스 마이그레이션 실행 (`023_add_display_config_to_flow_step.sql`)
|
||||
- [ ] 플로우 관리 페이지에서 스텝 속성 패널 열기
|
||||
- [ ] "표시 설정" 카드에서 컬럼 선택 및 저장
|
||||
- [ ] 플로우 위젯에서 선택한 컬럼만 표시되는지 확인
|
||||
- [ ] 화면관리에서 특정 스텝의 컬럼 오버라이드 설정
|
||||
- [ ] 오버라이드가 우선 적용되는지 확인
|
||||
- [ ] 오버라이드 초기화 버튼 동작 확인
|
||||
|
||||
### 우선순위 테스트
|
||||
|
||||
1. **모든 설정 없음**: 전체 컬럼 표시 ✓
|
||||
2. **플로우 기본 설정만**: 선택한 컬럼만 표시 ✓
|
||||
3. **화면 오버라이드 설정**: 오버라이드된 컬럼만 표시 (최우선) ✓
|
||||
|
||||
### 엣지 케이스
|
||||
|
||||
- [ ] 컬럼이 없는 테이블 선택 시
|
||||
- [ ] 모든 컬럼 선택 해제 시 (빈 배열) → 전체 표시
|
||||
- [ ] 존재하지 않는 컬럼명 선택 시 (무시)
|
||||
- [ ] 플로우 삭제 후 화면에서 참조하는 경우
|
||||
|
||||
---
|
||||
|
||||
## 📝 주의사항
|
||||
|
||||
### 1. 데이터베이스 마이그레이션
|
||||
|
||||
- 운영 환경에 배포하기 전에 반드시 마이그레이션 실행 필요
|
||||
- 기존 데이터에는 영향 없음 (NULL 허용)
|
||||
|
||||
### 2. 하위 호환성
|
||||
|
||||
- `displayConfig`가 없으면 자동으로 전체 컬럼 표시 (기존 동작 유지)
|
||||
- `stepColumnConfig`가 없으면 플로우 기본 설정 사용
|
||||
|
||||
### 3. 성능
|
||||
|
||||
- 컬럼 목록 로드는 테이블 선택 시 1회만 실행
|
||||
- GIN 인덱스로 JSONB 검색 최적화
|
||||
|
||||
---
|
||||
|
||||
## 🚀 향후 개선 방안 (선택사항)
|
||||
|
||||
### 1. 컬럼 순서 변경 기능
|
||||
|
||||
- Drag & Drop으로 컬럼 순서 변경
|
||||
- `columnOrder` 필드 활용
|
||||
|
||||
### 2. 컬럼별 커스텀 라벨
|
||||
|
||||
- 표시명을 사용자가 직접 지정
|
||||
- `columnLabels` 필드 활용
|
||||
|
||||
### 3. 컬럼 너비 설정
|
||||
|
||||
- 각 컬럼의 표시 너비 조정
|
||||
- `columnWidths` 필드 활용
|
||||
|
||||
### 4. 빠른 프리셋
|
||||
|
||||
- "기본 정보만", "전체 정보", "요약 정보" 등 프리셋 제공
|
||||
|
||||
---
|
||||
|
||||
## 📚 관련 파일 목록
|
||||
|
||||
### 데이터베이스
|
||||
|
||||
- `db/migrations/023_add_display_config_to_flow_step.sql`
|
||||
|
||||
### 백엔드
|
||||
|
||||
- `backend-node/src/types/flow.ts`
|
||||
|
||||
### 프론트엔드 (타입)
|
||||
|
||||
- `frontend/types/flow.ts`
|
||||
- `frontend/types/screen-management.ts`
|
||||
|
||||
### 프론트엔드 (컴포넌트)
|
||||
|
||||
- `frontend/components/screen/widgets/FlowWidget.tsx`
|
||||
- `frontend/components/flow/FlowStepPanel.tsx`
|
||||
- `frontend/components/screen/config-panels/FlowWidgetConfigPanel.tsx`
|
||||
|
||||
---
|
||||
|
||||
## ✅ 구현 완료
|
||||
|
||||
모든 Phase가 성공적으로 완료되었습니다!
|
||||
|
||||
**구현 날짜**: 2025-10-24
|
||||
|
||||
**구현 방식**: 하이브리드 (플로우 에디터 기본 설정 + 화면관리 오버라이드)
|
||||
|
||||
**적용 범위**:
|
||||
|
||||
- 플로우 관리 시스템 (플로우 정의 편집)
|
||||
- 화면관리 시스템 (플로우 위젯 설정)
|
||||
- 실제 화면 표시 (플로우 위젯 렌더링)
|
||||
Reference in New Issue
Block a user