From 67e838dc03494e528e78d573bb38200cfc4e8d0d Mon Sep 17 00:00:00 2001 From: dohyeons Date: Thu, 23 Oct 2025 17:44:12 +0900 Subject: [PATCH 1/3] =?UTF-8?q?package-lock=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9E=AC=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend-node/package-lock.json | 68 +++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/backend-node/package-lock.json b/backend-node/package-lock.json index 81adfc5c..b9528ee0 100644 --- a/backend-node/package-lock.json +++ b/backend-node/package-lock.json @@ -7605,7 +7605,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -7949,6 +7948,19 @@ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -7992,22 +8004,6 @@ "tlds": "1.260.0" } }, - "node_modules/mailparser/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/mailparser/node_modules/nodemailer": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.9.tgz", @@ -8021,6 +8017,7 @@ "version": "5.4.6", "resolved": "https://registry.npmjs.org/mailsplit/-/mailsplit-5.4.6.tgz", "integrity": "sha512-M+cqmzaPG/mEiCDmqQUz8L177JZLZmXAUpq38owtpq2xlXlTSw+kntnxRt2xsxVFFV6+T8Mj/U0l5s7s6e0rNw==", + "deprecated": "This package has been renamed to @zone-eu/mailsplit. Please update your dependencies.", "license": "(MIT OR EUPL-1.1+)", "dependencies": { "libbase64": "1.3.0", @@ -9268,6 +9265,33 @@ "node": ">=0.10.0" } }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -9586,6 +9610,16 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/selderee": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz", From c228ddb4987012a827376fb9a3ea0ccccc6b4ed6 Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 23 Oct 2025 17:55:04 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=EC=82=AD=EC=A0=9C=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EB=8F=99=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/(main)/screens/[screenId]/page.tsx | 10 +++ .../screen/RealtimePreviewDynamic.tsx | 6 ++ .../components/screen/widgets/FlowWidget.tsx | 85 +++++++++++++++---- .../lib/registry/DynamicComponentRenderer.tsx | 8 ++ .../button-primary/ButtonPrimaryComponent.tsx | 14 ++- .../flow-widget/FlowWidgetRenderer.tsx | 15 +--- frontend/lib/utils/buttonActions.ts | 14 ++- 7 files changed, 120 insertions(+), 32 deletions(-) diff --git a/frontend/app/(main)/screens/[screenId]/page.tsx b/frontend/app/(main)/screens/[screenId]/page.tsx index cd01afd4..82a8d27c 100644 --- a/frontend/app/(main)/screens/[screenId]/page.tsx +++ b/frontend/app/(main)/screens/[screenId]/page.tsx @@ -34,6 +34,9 @@ export default function ScreenViewPage() { // 테이블 새로고침을 위한 키 (값이 변경되면 테이블이 리렌더링됨) const [tableRefreshKey, setTableRefreshKey] = useState(0); + // 플로우 새로고침을 위한 키 (값이 변경되면 플로우 데이터가 리렌더링됨) + const [flowRefreshKey, setFlowRefreshKey] = useState(0); + // 편집 모달 상태 const [editModalOpen, setEditModalOpen] = useState(false); const [editModalConfig, setEditModalConfig] = useState<{ @@ -250,6 +253,13 @@ export default function ScreenViewPage() { setTableRefreshKey((prev) => prev + 1); setSelectedRowsData([]); // 선택 해제 }} + flowRefreshKey={flowRefreshKey} + onFlowRefresh={() => { + console.log("🔄 플로우 새로고침 요청됨"); + setFlowRefreshKey((prev) => prev + 1); + setFlowSelectedData([]); // 선택 해제 + setFlowSelectedStepId(null); + }} formData={formData} onFormDataChange={(fieldName, value) => { console.log("📝 폼 데이터 변경:", fieldName, "=", value); diff --git a/frontend/components/screen/RealtimePreviewDynamic.tsx b/frontend/components/screen/RealtimePreviewDynamic.tsx index 6ce31bfd..a00f972f 100644 --- a/frontend/components/screen/RealtimePreviewDynamic.tsx +++ b/frontend/components/screen/RealtimePreviewDynamic.tsx @@ -45,6 +45,8 @@ interface RealtimePreviewProps { onFlowSelectedDataChange?: (selectedData: any[], stepId: number | null) => void; refreshKey?: number; onRefresh?: () => void; + flowRefreshKey?: number; + onFlowRefresh?: () => void; // 폼 데이터 관련 props formData?: Record; @@ -101,6 +103,8 @@ export const RealtimePreviewDynamic: React.FC = ({ onFlowSelectedDataChange, refreshKey, onRefresh, + flowRefreshKey, + onFlowRefresh, formData, onFormDataChange, }) => { @@ -299,6 +303,8 @@ export const RealtimePreviewDynamic: React.FC = ({ onFlowSelectedDataChange={onFlowSelectedDataChange} refreshKey={refreshKey} onRefresh={onRefresh} + flowRefreshKey={flowRefreshKey} + onFlowRefresh={onFlowRefresh} formData={formData} onFormDataChange={onFormDataChange} /> diff --git a/frontend/components/screen/widgets/FlowWidget.tsx b/frontend/components/screen/widgets/FlowWidget.tsx index 6ef38e90..c232e2a2 100644 --- a/frontend/components/screen/widgets/FlowWidget.tsx +++ b/frontend/components/screen/widgets/FlowWidget.tsx @@ -32,9 +32,11 @@ interface FlowWidgetProps { component: FlowComponent; onStepClick?: (stepId: number, stepName: string) => void; onSelectedDataChange?: (selectedData: any[], stepId: number | null) => void; + flowRefreshKey?: number; // 새로고침 키 + onFlowRefresh?: () => void; // 새로고침 완료 콜백 } -export function FlowWidget({ component, onStepClick, onSelectedDataChange }: FlowWidgetProps) { +export function FlowWidget({ component, onStepClick, onSelectedDataChange, flowRefreshKey, onFlowRefresh }: FlowWidgetProps) { const [flowData, setFlowData] = useState(null); const [steps, setSteps] = useState([]); const [stepCounts, setStepCounts] = useState>({}); @@ -66,23 +68,66 @@ export function FlowWidget({ component, onStepClick, onSelectedDataChange }: Flo const showStepCount = config.showStepCount !== false && component.showStepCount !== false; // 기본값 true const allowDataMove = config.allowDataMove || component.allowDataMove || false; - console.log("🔍 FlowWidget 렌더링:", { - component, - componentConfig: config, - flowId, - flowName, - displayMode, - showStepCount, - allowDataMove, - }); + + // 선택된 스텝의 데이터를 다시 로드하는 함수 + const refreshStepData = async () => { + if (!flowId) return; + + try { + // 스텝 카운트는 항상 업데이트 (선택된 스텝 유무와 관계없이) + const countsResponse = await getAllStepCounts(flowId); + console.log("📊 스텝 카운트 API 응답:", countsResponse); + + if (countsResponse.success && countsResponse.data) { + // Record 형태로 변환 + const countsMap: Record = {}; + if (Array.isArray(countsResponse.data)) { + countsResponse.data.forEach((item: any) => { + countsMap[item.stepId] = item.count; + }); + } else if (typeof countsResponse.data === 'object') { + Object.assign(countsMap, countsResponse.data); + } + + console.log("✅ 스텝 카운트 업데이트:", countsMap); + setStepCounts(countsMap); + } + + // 선택된 스텝이 있으면 해당 스텝의 데이터도 새로고침 + if (selectedStepId) { + setStepDataLoading(true); + + const response = await getStepDataList(flowId, selectedStepId, 1, 100); + + if (!response.success) { + throw new Error(response.message || "데이터를 불러올 수 없습니다"); + } + + const rows = response.data?.records || []; + setStepData(rows); + + // 컬럼 추출 + if (rows.length > 0) { + setStepDataColumns(Object.keys(rows[0])); + } else { + setStepDataColumns([]); + } + + // 선택 초기화 + setSelectedRows(new Set()); + onSelectedDataChange?.([], selectedStepId); + } + } catch (err: any) { + console.error("❌ 플로우 새로고침 실패:", err); + toast.error(err.message || "데이터를 새로고치는데 실패했습니다"); + } finally { + if (selectedStepId) { + setStepDataLoading(false); + } + } + }; useEffect(() => { - console.log("🔍 FlowWidget useEffect 실행:", { - flowId, - hasFlowId: !!flowId, - config, - }); - if (!flowId) { setLoading(false); return; @@ -144,6 +189,14 @@ export function FlowWidget({ component, onStepClick, onSelectedDataChange }: Flo loadFlowData(); }, [flowId, showStepCount]); + // flowRefreshKey가 변경될 때마다 스텝 데이터 새로고침 + useEffect(() => { + if (flowRefreshKey !== undefined && flowRefreshKey > 0 && flowId) { + console.log("🔄 플로우 새로고침 실행, flowRefreshKey:", flowRefreshKey); + refreshStepData(); + } + }, [flowRefreshKey]); + // 스텝 클릭 핸들러 const handleStepClick = async (stepId: number, stepName: string) => { if (onStepClick) { diff --git a/frontend/lib/registry/DynamicComponentRenderer.tsx b/frontend/lib/registry/DynamicComponentRenderer.tsx index 970a74a0..9f107453 100644 --- a/frontend/lib/registry/DynamicComponentRenderer.tsx +++ b/frontend/lib/registry/DynamicComponentRenderer.tsx @@ -105,6 +105,9 @@ export interface DynamicComponentRendererProps { onFlowSelectedDataChange?: (selectedData: any[], stepId: number | null) => void; // 테이블 새로고침 키 refreshKey?: number; + // 플로우 새로고침 키 + flowRefreshKey?: number; + onFlowRefresh?: () => void; // 편집 모드 mode?: "view" | "edit"; // 모달 내에서 렌더링 여부 @@ -186,6 +189,8 @@ export const DynamicComponentRenderer: React.FC = flowSelectedStepId, onFlowSelectedDataChange, refreshKey, + flowRefreshKey, // Added this + onFlowRefresh, // Added this onConfigChange, isPreview, autoGeneration, @@ -307,6 +312,9 @@ export const DynamicComponentRenderer: React.FC = // 설정 변경 핸들러 전달 onConfigChange, refreshKey, + // 플로우 새로고침 키 + flowRefreshKey, + onFlowRefresh, // 반응형 모드 플래그 전달 isPreview, // 디자인 모드 플래그 전달 - isPreview와 명확히 구분 diff --git a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx index f77fd120..fe715aa7 100644 --- a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx +++ b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx @@ -29,6 +29,7 @@ export interface ButtonPrimaryComponentProps extends ComponentRendererProps { tableName?: string; onRefresh?: () => void; onClose?: () => void; + onFlowRefresh?: () => void; // 폼 데이터 관련 originalData?: Record; // 부분 업데이트용 원본 데이터 @@ -64,6 +65,7 @@ export const ButtonPrimaryComponent: React.FC = ({ tableName, onRefresh, onClose, + onFlowRefresh, selectedRows, selectedRowsData, flowSelectedData, @@ -418,8 +420,14 @@ export const ButtonPrimaryComponent: React.FC = ({ }); // 삭제 액션인데 선택된 데이터가 없으면 경고 메시지 표시하고 중단 - if (processedConfig.action.type === "delete" && (!selectedRowsData || selectedRowsData.length === 0)) { - console.log("⚠️ 삭제할 데이터가 선택되지 않았습니다."); + const hasDataToDelete = + (selectedRowsData && selectedRowsData.length > 0) || (flowSelectedData && flowSelectedData.length > 0); + + if (processedConfig.action.type === "delete" && !hasDataToDelete) { + console.log("⚠️ 삭제할 데이터가 선택되지 않았습니다.", { + hasSelectedRowsData: !!(selectedRowsData && selectedRowsData.length > 0), + hasFlowSelectedData: !!(flowSelectedData && flowSelectedData.length > 0), + }); toast.warning("삭제할 항목을 먼저 선택해주세요."); return; } @@ -432,6 +440,7 @@ export const ButtonPrimaryComponent: React.FC = ({ onFormDataChange, onRefresh, onClose, + onFlowRefresh, // 플로우 새로고침 콜백 추가 // 테이블 선택된 행 정보 추가 selectedRows, selectedRowsData, @@ -517,6 +526,7 @@ export const ButtonPrimaryComponent: React.FC = ({ onSelectedRowsChange: _onSelectedRowsChange, flowSelectedData: _flowSelectedData, // 플로우 선택 데이터 필터링 flowSelectedStepId: _flowSelectedStepId, // 플로우 선택 스텝 ID 필터링 + onFlowRefresh: _onFlowRefresh, // 플로우 새로고침 콜백 필터링 originalData: _originalData, // 부분 업데이트용 원본 데이터 필터링 refreshKey: _refreshKey, // 필터링 추가 isInModal: _isInModal, // 필터링 추가 diff --git a/frontend/lib/registry/components/flow-widget/FlowWidgetRenderer.tsx b/frontend/lib/registry/components/flow-widget/FlowWidgetRenderer.tsx index f75510c5..d6ac4dd2 100644 --- a/frontend/lib/registry/components/flow-widget/FlowWidgetRenderer.tsx +++ b/frontend/lib/registry/components/flow-widget/FlowWidgetRenderer.tsx @@ -20,23 +20,12 @@ export class FlowWidgetRenderer extends AutoRegisteringComponentRenderer { })(); render(): React.ReactElement { - 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 ( ); } diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 5f29de70..bbd8bbed 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -59,6 +59,7 @@ export interface ButtonActionContext { onFormDataChange?: (fieldName: string, value: any) => void; onClose?: () => void; onRefresh?: () => void; + onFlowRefresh?: () => void; // 플로우 새로고침 콜백 // 테이블 선택된 행 정보 (다중 선택 액션용) selectedRows?: any[]; @@ -216,7 +217,9 @@ export class ButtonActionExecutor { throw new Error("저장에 필요한 정보가 부족합니다. (테이블명 또는 화면ID 누락)"); } + // 테이블과 플로우 모두 새로고침 context.onRefresh?.(); + context.onFlowRefresh?.(); return true; } catch (error) { console.error("저장 오류:", error); @@ -402,7 +405,16 @@ export class ButtonActionExecutor { } console.log(`✅ 다중 삭제 성공: ${dataToDelete.length}개 항목`); - context.onRefresh?.(); // 테이블 새로고침 + + // 데이터 소스에 따라 적절한 새로고침 호출 + if (flowSelectedData && flowSelectedData.length > 0) { + console.log("🔄 플로우 데이터 삭제 완료, 플로우 새로고침 호출"); + context.onFlowRefresh?.(); // 플로우 새로고침 + } else { + console.log("🔄 테이블 데이터 삭제 완료, 테이블 새로고침 호출"); + context.onRefresh?.(); // 테이블 새로고침 + } + return true; } From 43654f7516d41d275c53f8ac728fbcd8556ea69e Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 23 Oct 2025 17:55:24 +0900 Subject: [PATCH 3/3] Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into feature/screen-management