Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into feat/dashboard

This commit is contained in:
dohyeons 2025-10-23 17:27:10 +09:00
commit bb926b1c58
13 changed files with 228 additions and 33 deletions

View File

@ -27,6 +27,10 @@ export default function ScreenViewPage() {
// 테이블에서 선택된 행 데이터 (버튼 액션에 전달) // 테이블에서 선택된 행 데이터 (버튼 액션에 전달)
const [selectedRowsData, setSelectedRowsData] = useState<any[]>([]); const [selectedRowsData, setSelectedRowsData] = useState<any[]>([]);
// 플로우에서 선택된 데이터 (버튼 액션에 전달)
const [flowSelectedData, setFlowSelectedData] = useState<any[]>([]);
const [flowSelectedStepId, setFlowSelectedStepId] = useState<number | null>(null);
// 테이블 새로고침을 위한 키 (값이 변경되면 테이블이 리렌더링됨) // 테이블 새로고침을 위한 키 (값이 변경되면 테이블이 리렌더링됨)
const [tableRefreshKey, setTableRefreshKey] = useState(0); const [tableRefreshKey, setTableRefreshKey] = useState(0);
@ -228,6 +232,18 @@ export default function ScreenViewPage() {
console.log("🔍 화면에서 선택된 행 데이터:", selectedData); console.log("🔍 화면에서 선택된 행 데이터:", selectedData);
setSelectedRowsData(selectedData); setSelectedRowsData(selectedData);
}} }}
flowSelectedData={flowSelectedData}
flowSelectedStepId={flowSelectedStepId}
onFlowSelectedDataChange={(selectedData: any[], stepId: number | null) => {
console.log("🔍 [page.tsx] 플로우 선택된 데이터 받음:", {
dataCount: selectedData.length,
selectedData,
stepId,
});
setFlowSelectedData(selectedData);
setFlowSelectedStepId(stepId);
console.log("🔍 [page.tsx] 상태 업데이트 완료");
}}
refreshKey={tableRefreshKey} refreshKey={tableRefreshKey}
onRefresh={() => { onRefresh={() => {
console.log("🔄 테이블 새로고침 요청됨"); console.log("🔄 테이블 새로고침 요청됨");

View File

@ -51,6 +51,10 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
// 테이블에서 선택된 행 데이터 (버튼 액션에 전달) // 테이블에서 선택된 행 데이터 (버튼 액션에 전달)
const [selectedRowsData, setSelectedRowsData] = useState<any[]>([]); const [selectedRowsData, setSelectedRowsData] = useState<any[]>([]);
// 플로우에서 선택된 데이터 (버튼 액션에 전달)
const [flowSelectedData, setFlowSelectedData] = useState<any[]>([]);
const [flowSelectedStepId, setFlowSelectedStepId] = useState<number | null>(null);
// 팝업 화면 상태 // 팝업 화면 상태
const [popupScreen, setPopupScreen] = useState<{ const [popupScreen, setPopupScreen] = useState<{
screenId: number; screenId: number;
@ -194,6 +198,13 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
console.log("🔍 테이블에서 선택된 행 데이터:", selectedData); console.log("🔍 테이블에서 선택된 행 데이터:", selectedData);
setSelectedRowsData(selectedData); setSelectedRowsData(selectedData);
}} }}
flowSelectedData={flowSelectedData}
flowSelectedStepId={flowSelectedStepId}
onFlowSelectedDataChange={(selectedData, stepId) => {
console.log("🔍 플로우에서 선택된 데이터:", { selectedData, stepId });
setFlowSelectedData(selectedData);
setFlowSelectedStepId(stepId);
}}
onRefresh={() => { onRefresh={() => {
console.log("🔄 버튼에서 테이블 새로고침 요청됨"); console.log("🔄 버튼에서 테이블 새로고침 요청됨");
// 테이블 컴포넌트는 자체적으로 loadData 호출 // 테이블 컴포넌트는 자체적으로 loadData 호출

View File

@ -22,6 +22,10 @@ interface OptimizedButtonProps {
formData?: Record<string, any>; formData?: Record<string, any>;
companyCode?: string; companyCode?: string;
disabled?: boolean; disabled?: boolean;
selectedRows?: any[];
selectedRowsData?: any[];
flowSelectedData?: any[];
flowSelectedStepId?: number | null;
} }
/** /**
@ -41,6 +45,10 @@ export const OptimizedButtonComponent: React.FC<OptimizedButtonProps> = ({
formData = {}, formData = {},
companyCode = "DEFAULT", companyCode = "DEFAULT",
disabled = false, disabled = false,
selectedRows = [],
selectedRowsData = [],
flowSelectedData = [],
flowSelectedStepId = null,
}) => { }) => {
// 🔥 상태 관리 // 🔥 상태 관리
const [isExecuting, setIsExecuting] = useState(false); const [isExecuting, setIsExecuting] = useState(false);
@ -79,6 +87,8 @@ export const OptimizedButtonComponent: React.FC<OptimizedButtonProps> = ({
formData, formData,
selectedRows: selectedRows || [], selectedRows: selectedRows || [],
selectedRowsData: selectedRowsData || [], selectedRowsData: selectedRowsData || [],
flowSelectedData: flowSelectedData || [],
flowSelectedStepId: flowSelectedStepId,
controlDataSource: config?.dataflowConfig?.controlDataSource || "form", controlDataSource: config?.dataflowConfig?.controlDataSource || "form",
buttonId: component.id, buttonId: component.id,
componentData: component, componentData: component,

View File

@ -60,6 +60,8 @@ interface RealtimePreviewProps {
onDragEnd?: () => void; onDragEnd?: () => void;
onGroupToggle?: (groupId: string) => void; // 그룹 접기/펼치기 onGroupToggle?: (groupId: string) => void; // 그룹 접기/펼치기
children?: React.ReactNode; // 그룹 내 자식 컴포넌트들 children?: React.ReactNode; // 그룹 내 자식 컴포넌트들
// 플로우 선택 데이터 전달용
onFlowSelectedDataChange?: (selectedData: any[], stepId: number | null) => void;
} }
// 영역 레이아웃에 따른 아이콘 반환 // 영역 레이아웃에 따른 아이콘 반환
@ -218,6 +220,7 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
onDragEnd, onDragEnd,
onGroupToggle, onGroupToggle,
children, children,
onFlowSelectedDataChange,
}) => { }) => {
const { user } = useAuth(); const { user } = useAuth();
const { type, id, position, size, style = {} } = component; const { type, id, position, size, style = {} } = component;
@ -494,7 +497,10 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
return ( return (
<div className="h-auto w-full"> <div className="h-auto w-full">
<FlowWidget component={flowComponent as any} /> <FlowWidget
component={flowComponent as any}
onSelectedDataChange={onFlowSelectedDataChange}
/>
</div> </div>
); );
})()} })()}

View File

@ -40,6 +40,9 @@ interface RealtimePreviewProps {
tableName?: string; tableName?: string;
selectedRowsData?: any[]; selectedRowsData?: any[];
onSelectedRowsChange?: (selectedRows: any[], selectedRowsData: any[]) => void; onSelectedRowsChange?: (selectedRows: any[], selectedRowsData: any[]) => void;
flowSelectedData?: any[];
flowSelectedStepId?: number | null;
onFlowSelectedDataChange?: (selectedData: any[], stepId: number | null) => void;
refreshKey?: number; refreshKey?: number;
onRefresh?: () => void; onRefresh?: () => void;
@ -93,6 +96,9 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
tableName, tableName,
selectedRowsData, selectedRowsData,
onSelectedRowsChange, onSelectedRowsChange,
flowSelectedData,
flowSelectedStepId,
onFlowSelectedDataChange,
refreshKey, refreshKey,
onRefresh, onRefresh,
formData, formData,
@ -288,6 +294,9 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
tableName={tableName} tableName={tableName}
selectedRowsData={selectedRowsData} selectedRowsData={selectedRowsData}
onSelectedRowsChange={onSelectedRowsChange} onSelectedRowsChange={onSelectedRowsChange}
flowSelectedData={flowSelectedData}
flowSelectedStepId={flowSelectedStepId}
onFlowSelectedDataChange={onFlowSelectedDataChange}
refreshKey={refreshKey} refreshKey={refreshKey}
onRefresh={onRefresh} onRefresh={onRefresh}
formData={formData} formData={formData}

View File

@ -31,9 +31,10 @@ import {
interface FlowWidgetProps { interface FlowWidgetProps {
component: FlowComponent; component: FlowComponent;
onStepClick?: (stepId: number, stepName: string) => void; onStepClick?: (stepId: number, stepName: string) => void;
onSelectedDataChange?: (selectedData: any[], stepId: number | null) => void;
} }
export function FlowWidget({ component, onStepClick }: FlowWidgetProps) { export function FlowWidget({ component, onStepClick, onSelectedDataChange }: FlowWidgetProps) {
const [flowData, setFlowData] = useState<FlowDefinition | null>(null); const [flowData, setFlowData] = useState<FlowDefinition | null>(null);
const [steps, setSteps] = useState<FlowStep[]>([]); const [steps, setSteps] = useState<FlowStep[]>([]);
const [stepCounts, setStepCounts] = useState<Record<number, number>>({}); const [stepCounts, setStepCounts] = useState<Record<number, number>>({});
@ -156,6 +157,8 @@ export function FlowWidget({ component, onStepClick }: FlowWidgetProps) {
setStepData([]); setStepData([]);
setStepDataColumns([]); setStepDataColumns([]);
setSelectedRows(new Set()); setSelectedRows(new Set());
// 선택 초기화 전달
onSelectedDataChange?.([], null);
return; return;
} }
@ -163,6 +166,8 @@ export function FlowWidget({ component, onStepClick }: FlowWidgetProps) {
setSelectedStepId(stepId); setSelectedStepId(stepId);
setStepDataLoading(true); setStepDataLoading(true);
setSelectedRows(new Set()); setSelectedRows(new Set());
// 선택 초기화 전달
onSelectedDataChange?.([], stepId);
try { try {
const response = await getStepDataList(flowId!, stepId, 1, 100); const response = await getStepDataList(flowId!, stepId, 1, 100);
@ -197,15 +202,32 @@ export function FlowWidget({ component, onStepClick }: FlowWidgetProps) {
newSelected.add(rowIndex); newSelected.add(rowIndex);
} }
setSelectedRows(newSelected); setSelectedRows(newSelected);
// 선택된 데이터를 상위로 전달
const selectedData = Array.from(newSelected).map((index) => stepData[index]);
console.log("🌊 FlowWidget - 체크박스 토글, 상위로 전달:", {
rowIndex,
newSelectedSize: newSelected.size,
selectedData,
selectedStepId,
hasCallback: !!onSelectedDataChange,
});
onSelectedDataChange?.(selectedData, selectedStepId);
}; };
// 전체 선택/해제 // 전체 선택/해제
const toggleAllRows = () => { const toggleAllRows = () => {
let newSelected: Set<number>;
if (selectedRows.size === stepData.length) { if (selectedRows.size === stepData.length) {
setSelectedRows(new Set()); newSelected = new Set();
} else { } else {
setSelectedRows(new Set(stepData.map((_, index) => index))); newSelected = new Set(stepData.map((_, index) => index));
} }
setSelectedRows(newSelected);
// 선택된 데이터를 상위로 전달
const selectedData = Array.from(newSelected).map((index) => stepData[index]);
onSelectedDataChange?.(selectedData, selectedStepId);
}; };
// 현재 단계에서 가능한 다음 단계들 찾기 // 현재 단계에서 가능한 다음 단계들 찾기
@ -264,6 +286,8 @@ export function FlowWidget({ component, onStepClick }: FlowWidgetProps) {
// 선택 초기화 // 선택 초기화
setSelectedNextStepId(null); setSelectedNextStepId(null);
setSelectedRows(new Set()); setSelectedRows(new Set());
// 선택 초기화 전달
onSelectedDataChange?.([], selectedStepId);
// 데이터 새로고침 // 데이터 새로고침
await handleStepClick(selectedStepId, steps.find((s) => s.id === selectedStepId)?.stepName || ""); await handleStepClick(selectedStepId, steps.find((s) => s.id === selectedStepId)?.stepName || "");

View File

@ -30,6 +30,10 @@ export interface ComponentRenderer {
selectedRows?: any[]; selectedRows?: any[];
selectedRowsData?: any[]; selectedRowsData?: any[];
onSelectedRowsChange?: (selectedRows: any[], selectedRowsData: any[]) => void; onSelectedRowsChange?: (selectedRows: any[], selectedRowsData: any[]) => void;
// 플로우 선택된 데이터 정보 (플로우 위젯 선택 액션용)
flowSelectedData?: any[];
flowSelectedStepId?: number | null;
onFlowSelectedDataChange?: (selectedData: any[], stepId: number | null) => void;
// 테이블 새로고침 키 // 테이블 새로고침 키
refreshKey?: number; refreshKey?: number;
// 편집 모드 // 편집 모드
@ -95,6 +99,10 @@ export interface DynamicComponentRendererProps {
selectedRows?: any[]; selectedRows?: any[];
selectedRowsData?: any[]; selectedRowsData?: any[];
onSelectedRowsChange?: (selectedRows: any[], selectedRowsData: any[]) => void; onSelectedRowsChange?: (selectedRows: any[], selectedRowsData: any[]) => void;
// 플로우 선택된 데이터 정보 (플로우 위젯 선택 액션용)
flowSelectedData?: any[];
flowSelectedStepId?: number | null;
onFlowSelectedDataChange?: (selectedData: any[], stepId: number | null) => void;
// 테이블 새로고침 키 // 테이블 새로고침 키
refreshKey?: number; refreshKey?: number;
// 편집 모드 // 편집 모드
@ -174,6 +182,9 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
selectedRows, selectedRows,
selectedRowsData, selectedRowsData,
onSelectedRowsChange, onSelectedRowsChange,
flowSelectedData,
flowSelectedStepId,
onFlowSelectedDataChange,
refreshKey, refreshKey,
onConfigChange, onConfigChange,
isPreview, isPreview,
@ -289,6 +300,10 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
selectedRows, selectedRows,
selectedRowsData, selectedRowsData,
onSelectedRowsChange, onSelectedRowsChange,
// 플로우 선택된 데이터 정보 전달
flowSelectedData,
flowSelectedStepId,
onFlowSelectedDataChange,
// 설정 변경 핸들러 전달 // 설정 변경 핸들러 전달
onConfigChange, onConfigChange,
refreshKey, refreshKey,
@ -299,16 +314,26 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
}; };
// 렌더러가 클래스인지 함수인지 확인 // 렌더러가 클래스인지 함수인지 확인
console.log("🔍🔍 DynamicComponentRenderer - 렌더러 타입 확인:", {
componentType,
isFunction: typeof NewComponentRenderer === "function",
hasPrototype: !!NewComponentRenderer.prototype,
hasRenderMethod: !!NewComponentRenderer.prototype?.render,
rendererName: NewComponentRenderer.name,
});
if ( if (
typeof NewComponentRenderer === "function" && typeof NewComponentRenderer === "function" &&
NewComponentRenderer.prototype && NewComponentRenderer.prototype &&
NewComponentRenderer.prototype.render NewComponentRenderer.prototype.render
) { ) {
// 클래스 기반 렌더러 (AutoRegisteringComponentRenderer 상속) // 클래스 기반 렌더러 (AutoRegisteringComponentRenderer 상속)
console.log("✅ 클래스 기반 렌더러로 렌더링:", componentType);
const rendererInstance = new NewComponentRenderer(rendererProps); const rendererInstance = new NewComponentRenderer(rendererProps);
return rendererInstance.render(); return rendererInstance.render();
} else { } else {
// 함수형 컴포넌트 // 함수형 컴포넌트
console.log("✅ 함수형 컴포넌트로 렌더링:", componentType);
return <NewComponentRenderer {...rendererProps} />; return <NewComponentRenderer {...rendererProps} />;
} }
} }
@ -371,6 +396,10 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
selectedRows: props.selectedRows, selectedRows: props.selectedRows,
selectedRowsData: props.selectedRowsData, selectedRowsData: props.selectedRowsData,
onSelectedRowsChange: props.onSelectedRowsChange, onSelectedRowsChange: props.onSelectedRowsChange,
// 플로우 선택된 데이터 정보 전달
flowSelectedData: props.flowSelectedData,
flowSelectedStepId: props.flowSelectedStepId,
onFlowSelectedDataChange: props.onFlowSelectedDataChange,
refreshKey: props.refreshKey, refreshKey: props.refreshKey,
// DOM 안전한 props들 // DOM 안전한 props들
...safeLegacyProps, ...safeLegacyProps,

View File

@ -36,6 +36,10 @@ export interface ButtonPrimaryComponentProps extends ComponentRendererProps {
// 테이블 선택된 행 정보 (다중 선택 액션용) // 테이블 선택된 행 정보 (다중 선택 액션용)
selectedRows?: any[]; selectedRows?: any[];
selectedRowsData?: any[]; selectedRowsData?: any[];
// 플로우 선택된 데이터 정보 (플로우 위젯 선택 액션용)
flowSelectedData?: any[];
flowSelectedStepId?: number | null;
} }
/** /**
@ -62,6 +66,8 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
onClose, onClose,
selectedRows, selectedRows,
selectedRowsData, selectedRowsData,
flowSelectedData,
flowSelectedStepId,
...props ...props
}) => { }) => {
console.log("🔵 ButtonPrimaryComponent 렌더링, 받은 props:", { console.log("🔵 ButtonPrimaryComponent 렌더링, 받은 props:", {
@ -69,6 +75,10 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
hasSelectedRowsData: !!selectedRowsData, hasSelectedRowsData: !!selectedRowsData,
selectedRowsDataLength: selectedRowsData?.length, selectedRowsDataLength: selectedRowsData?.length,
selectedRowsData, selectedRowsData,
hasFlowSelectedData: !!flowSelectedData,
flowSelectedDataLength: flowSelectedData?.length,
flowSelectedData,
flowSelectedStepId,
tableName, tableName,
screenId, screenId,
}); });
@ -425,12 +435,19 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
// 테이블 선택된 행 정보 추가 // 테이블 선택된 행 정보 추가
selectedRows, selectedRows,
selectedRowsData, selectedRowsData,
// 플로우 선택된 데이터 정보 추가
flowSelectedData,
flowSelectedStepId,
}; };
console.log("🔍 버튼 액션 실행 전 context 확인:", { console.log("🔍 버튼 액션 실행 전 context 확인:", {
hasSelectedRowsData: !!selectedRowsData, hasSelectedRowsData: !!selectedRowsData,
selectedRowsDataLength: selectedRowsData?.length, selectedRowsDataLength: selectedRowsData?.length,
selectedRowsData, selectedRowsData,
hasFlowSelectedData: !!flowSelectedData,
flowSelectedDataLength: flowSelectedData?.length,
flowSelectedData,
flowSelectedStepId,
tableName, tableName,
screenId, screenId,
formData, formData,
@ -498,6 +515,8 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
selectedRows: _selectedRows, selectedRows: _selectedRows,
selectedRowsData: _selectedRowsData, selectedRowsData: _selectedRowsData,
onSelectedRowsChange: _onSelectedRowsChange, onSelectedRowsChange: _onSelectedRowsChange,
flowSelectedData: _flowSelectedData, // 플로우 선택 데이터 필터링
flowSelectedStepId: _flowSelectedStepId, // 플로우 선택 스텝 ID 필터링
originalData: _originalData, // 부분 업데이트용 원본 데이터 필터링 originalData: _originalData, // 부분 업데이트용 원본 데이터 필터링
refreshKey: _refreshKey, // 필터링 추가 refreshKey: _refreshKey, // 필터링 추가
isInModal: _isInModal, // 필터링 추가 isInModal: _isInModal, // 필터링 추가

View File

@ -4,16 +4,41 @@ import React from "react";
import { AutoRegisteringComponentRenderer } from "../../AutoRegisteringComponentRenderer"; import { AutoRegisteringComponentRenderer } from "../../AutoRegisteringComponentRenderer";
import { FlowWidgetDefinition } from "./index"; import { FlowWidgetDefinition } from "./index";
import { FlowWidget } from "@/components/screen/widgets/FlowWidget"; import { FlowWidget } from "@/components/screen/widgets/FlowWidget";
import { createComponentDefinition } from "../../utils/createComponentDefinition";
/** /**
* FlowWidget * FlowWidget
* *
*/ */
export class FlowWidgetRenderer extends AutoRegisteringComponentRenderer { export class FlowWidgetRenderer extends AutoRegisteringComponentRenderer {
static componentDefinition = FlowWidgetDefinition; // 먼저 Definition에 컴포넌트 설정
static componentDefinition = (() => {
// FlowWidgetRenderer를 컴포넌트로 설정
const definition = { ...FlowWidgetDefinition, component: FlowWidgetRenderer };
// createComponentDefinition으로 검증 및 처리
return createComponentDefinition(definition as any);
})();
render(): React.ReactElement { render(): React.ReactElement {
return <FlowWidget component={this.props.component as any} />; console.log("🎨🎨🎨 FlowWidgetRenderer - render 호출 시작 🎨🎨🎨");
console.log("🎨 FlowWidgetRenderer - render 호출:", {
componentId: this.props.component.id,
hasOnFlowSelectedDataChange: !!this.props.onFlowSelectedDataChange,
onFlowSelectedDataChangeType: typeof this.props.onFlowSelectedDataChange,
allPropsKeys: Object.keys(this.props),
allPropsValues: this.props,
});
console.log("🎨🎨🎨 FlowWidget에 전달할 prop:", {
hasComponent: !!this.props.component,
hasOnSelectedDataChange: !!this.props.onFlowSelectedDataChange,
});
return (
<FlowWidget
component={this.props.component as any}
onSelectedDataChange={this.props.onFlowSelectedDataChange}
/>
);
} }
} }

View File

@ -1,22 +1,20 @@
"use client"; "use client";
import { createComponentDefinition } from "../../utils/createComponentDefinition";
import { ComponentCategory } from "@/types/component"; import { ComponentCategory } from "@/types/component";
import { FlowWidget } from "@/components/screen/widgets/FlowWidget";
import { FlowWidgetConfigPanel } from "@/components/screen/config-panels/FlowWidgetConfigPanel"; import { FlowWidgetConfigPanel } from "@/components/screen/config-panels/FlowWidgetConfigPanel";
/** /**
* FlowWidget * FlowWidget ( FlowWidgetRenderer에서 )
* *
*/ */
export const FlowWidgetDefinition = createComponentDefinition({ export const FlowWidgetDefinition = {
id: "flow-widget", id: "flow-widget",
name: "플로우 위젯", name: "플로우 위젯",
nameEng: "Flow Widget", nameEng: "Flow Widget",
description: "플로우 관리 시스템의 플로우를 화면에 표시합니다", description: "플로우 관리 시스템의 플로우를 화면에 표시합니다",
category: ComponentCategory.DISPLAY, category: ComponentCategory.DISPLAY,
webType: "text", // 기본 웹타입 (필수) webType: "text" as const, // 기본 웹타입 (필수)
component: FlowWidget, component: null as any, // FlowWidgetRenderer에서 설정됩니다
defaultConfig: { defaultConfig: {
flowId: undefined, flowId: undefined,
flowName: undefined, flowName: undefined,
@ -30,11 +28,11 @@ export const FlowWidgetDefinition = createComponentDefinition({
gridColumnSpan: "full", // 전체 너비 사용 gridColumnSpan: "full", // 전체 너비 사용
}, },
configPanel: FlowWidgetConfigPanel, configPanel: FlowWidgetConfigPanel,
icon: "Workflow", icon: "Workflow" as const,
tags: ["플로우", "워크플로우", "프로세스", "상태"], tags: ["플로우", "워크플로우", "프로세스", "상태"],
version: "1.0.0", version: "1.0.0",
author: "개발팀", author: "개발팀",
documentation: "", documentation: "",
}); } as const;
// 컴포넌트는 FlowWidgetRenderer에서 자동 등록됩니다 // 컴포넌트는 FlowWidgetRenderer에서 자동 등록됩니다

View File

@ -64,6 +64,10 @@ export interface ButtonActionContext {
selectedRows?: any[]; selectedRows?: any[];
selectedRowsData?: any[]; selectedRowsData?: any[];
// 플로우 선택된 데이터 정보 (플로우 위젯 선택 액션용)
flowSelectedData?: any[];
flowSelectedStepId?: number | null;
// 제어 실행을 위한 추가 정보 // 제어 실행을 위한 추가 정보
buttonId?: string; buttonId?: string;
userId?: string; userId?: string;
@ -302,12 +306,23 @@ export class ButtonActionExecutor {
* *
*/ */
private static async handleDelete(config: ButtonActionConfig, context: ButtonActionContext): Promise<boolean> { private static async handleDelete(config: ButtonActionConfig, context: ButtonActionContext): Promise<boolean> {
const { formData, tableName, screenId, selectedRowsData } = context; const { formData, tableName, screenId, selectedRowsData, flowSelectedData } = context;
try { try {
// 다중 선택된 행이 있는 경우 (테이블에서 체크박스로 선택) // 플로우 선택 데이터 우선 사용
if (selectedRowsData && selectedRowsData.length > 0) { let dataToDelete = flowSelectedData && flowSelectedData.length > 0 ? flowSelectedData : selectedRowsData;
console.log(`다중 삭제 액션 실행: ${selectedRowsData.length}개 항목`, selectedRowsData);
console.log("🔍 handleDelete - 데이터 소스 확인:", {
hasFlowSelectedData: !!(flowSelectedData && flowSelectedData.length > 0),
flowSelectedDataLength: flowSelectedData?.length || 0,
hasSelectedRowsData: !!(selectedRowsData && selectedRowsData.length > 0),
selectedRowsDataLength: selectedRowsData?.length || 0,
dataToDeleteLength: dataToDelete?.length || 0,
});
// 다중 선택된 데이터가 있는 경우
if (dataToDelete && dataToDelete.length > 0) {
console.log(`다중 삭제 액션 실행: ${dataToDelete.length}개 항목`, dataToDelete);
// 테이블의 기본키 조회 // 테이블의 기본키 조회
let primaryKeys: string[] = []; let primaryKeys: string[] = [];
@ -324,7 +339,7 @@ export class ButtonActionExecutor {
} }
// 각 선택된 항목을 삭제 // 각 선택된 항목을 삭제
for (const rowData of selectedRowsData) { for (const rowData of dataToDelete) {
let deleteId: any = null; let deleteId: any = null;
// 1순위: 데이터베이스에서 조회한 기본키 사용 // 1순위: 데이터베이스에서 조회한 기본키 사용
@ -386,7 +401,7 @@ export class ButtonActionExecutor {
} }
} }
console.log(`✅ 다중 삭제 성공: ${selectedRowsData.length}개 항목`); console.log(`✅ 다중 삭제 성공: ${dataToDelete.length}개 항목`);
context.onRefresh?.(); // 테이블 새로고침 context.onRefresh?.(); // 테이블 새로고침
return true; return true;
} }
@ -581,10 +596,21 @@ export class ButtonActionExecutor {
* *
*/ */
private static handleEdit(config: ButtonActionConfig, context: ButtonActionContext): boolean { private static handleEdit(config: ButtonActionConfig, context: ButtonActionContext): boolean {
const { selectedRowsData } = context; const { selectedRowsData, flowSelectedData } = context;
// 선택된 행이 없는 경우 // 플로우 선택 데이터 우선 사용
if (!selectedRowsData || selectedRowsData.length === 0) { let dataToEdit = flowSelectedData && flowSelectedData.length > 0 ? flowSelectedData : selectedRowsData;
console.log("🔍 handleEdit - 데이터 소스 확인:", {
hasFlowSelectedData: !!(flowSelectedData && flowSelectedData.length > 0),
flowSelectedDataLength: flowSelectedData?.length || 0,
hasSelectedRowsData: !!(selectedRowsData && selectedRowsData.length > 0),
selectedRowsDataLength: selectedRowsData?.length || 0,
dataToEditLength: dataToEdit?.length || 0,
});
// 선택된 데이터가 없는 경우
if (!dataToEdit || dataToEdit.length === 0) {
toast.error("수정할 항목을 선택해주세요."); toast.error("수정할 항목을 선택해주세요.");
return false; return false;
} }
@ -595,15 +621,15 @@ export class ButtonActionExecutor {
return false; return false;
} }
console.log(`📝 편집 액션 실행: ${selectedRowsData.length}개 항목`, { console.log(`📝 편집 액션 실행: ${dataToEdit.length}개 항목`, {
selectedRowsData, dataToEdit,
targetScreenId: config.targetScreenId, targetScreenId: config.targetScreenId,
editMode: config.editMode, editMode: config.editMode,
}); });
if (selectedRowsData.length === 1) { if (dataToEdit.length === 1) {
// 단일 항목 편집 // 단일 항목 편집
const rowData = selectedRowsData[0]; const rowData = dataToEdit[0];
console.log("📝 단일 항목 편집:", rowData); console.log("📝 단일 항목 편집:", rowData);
this.openEditForm(config, rowData, context); this.openEditForm(config, rowData, context);
@ -710,6 +736,8 @@ export class ButtonActionExecutor {
formData: context.formData, formData: context.formData,
selectedRows: context.selectedRows, selectedRows: context.selectedRows,
selectedRowsData: context.selectedRowsData, selectedRowsData: context.selectedRowsData,
flowSelectedData: context.flowSelectedData,
flowSelectedStepId: context.flowSelectedStepId,
config, config,
}); });
@ -739,7 +767,10 @@ export class ButtonActionExecutor {
if (!controlDataSource) { if (!controlDataSource) {
// 설정이 없으면 자동 판단 // 설정이 없으면 자동 판단
if (context.selectedRowsData && context.selectedRowsData.length > 0) { if (context.flowSelectedData && context.flowSelectedData.length > 0) {
controlDataSource = "flow-selection";
console.log("🔄 자동 판단: flow-selection 모드 사용");
} else if (context.selectedRowsData && context.selectedRowsData.length > 0) {
controlDataSource = "table-selection"; controlDataSource = "table-selection";
console.log("🔄 자동 판단: table-selection 모드 사용"); console.log("🔄 자동 판단: table-selection 모드 사용");
} else if (context.formData && Object.keys(context.formData).length > 0) { } else if (context.formData && Object.keys(context.formData).length > 0) {
@ -755,6 +786,8 @@ export class ButtonActionExecutor {
formData: context.formData || {}, formData: context.formData || {},
selectedRows: context.selectedRows || [], selectedRows: context.selectedRows || [],
selectedRowsData: context.selectedRowsData || [], selectedRowsData: context.selectedRowsData || [],
flowSelectedData: context.flowSelectedData || [],
flowSelectedStepId: context.flowSelectedStepId,
controlDataSource, controlDataSource,
}; };
@ -779,11 +812,20 @@ export class ButtonActionExecutor {
// 노드 플로우 실행 API 호출 (API 클라이언트 사용) // 노드 플로우 실행 API 호출 (API 클라이언트 사용)
const { executeNodeFlow } = await import("@/lib/api/nodeFlows"); const { executeNodeFlow } = await import("@/lib/api/nodeFlows");
// 데이터 소스 준비: 선택된 행 또는 폼 데이터 // 데이터 소스 준비: 플로우 선택, 테이블 선택, 또는 폼 데이터
let sourceData: any = null; let sourceData: any = null;
let dataSourceType: string = "none"; let dataSourceType: string = "none";
if (context.selectedRowsData && context.selectedRowsData.length > 0) { if (context.flowSelectedData && context.flowSelectedData.length > 0) {
// 플로우에서 선택된 데이터 사용
sourceData = context.flowSelectedData;
dataSourceType = "flow-selection";
console.log("🌊 플로우 선택 데이터 사용:", {
stepId: context.flowSelectedStepId,
dataCount: sourceData.length,
sourceData,
});
} else if (context.selectedRowsData && context.selectedRowsData.length > 0) {
// 테이블에서 선택된 행 데이터 사용 // 테이블에서 선택된 행 데이터 사용
sourceData = context.selectedRowsData; sourceData = context.selectedRowsData;
dataSourceType = "table-selection"; dataSourceType = "table-selection";

View File

@ -23,6 +23,8 @@ export interface ButtonExecutionContext {
formData?: Record<string, any>; formData?: Record<string, any>;
selectedRows?: any[]; selectedRows?: any[];
tableData?: any[]; tableData?: any[];
flowSelectedData?: any[];
flowSelectedStepId?: number | null;
} }
export interface ExecutionResult { export interface ExecutionResult {

View File

@ -97,7 +97,7 @@ export interface ButtonDataflowConfig {
/** /**
* *
*/ */
export type ControlDataSource = "form" | "table-selection" | "both"; export type ControlDataSource = "form" | "table-selection" | "flow-selection" | "both";
/** /**
* *
@ -376,13 +376,17 @@ export interface ExtendedControlContext {
selectedRows?: unknown[]; selectedRows?: unknown[];
selectedRowsData?: Record<string, unknown>[]; selectedRowsData?: Record<string, unknown>[];
// 플로우 선택 데이터
flowSelectedData?: Record<string, unknown>[];
flowSelectedStepId?: number | null;
// 제어 데이터 소스 타입 // 제어 데이터 소스 타입
controlDataSource: ControlDataSource; controlDataSource: ControlDataSource;
// 기타 컨텍스트 // 기타 컨텍스트
buttonId: string; buttonId?: string;
componentData?: unknown; componentData?: unknown;
timestamp: string; timestamp?: string;
clickCount?: number; clickCount?: number;
// 사용자 정보 // 사용자 정보