"use client"; import React, { useState, useCallback, useEffect, useMemo } from "react"; import { Button } from "@/components/ui/button"; import { toast } from "react-hot-toast"; import { Loader2, CheckCircle2, AlertCircle, Clock, Workflow } from "lucide-react"; import { ComponentData, ButtonActionType } from "@/types/screen"; import { optimizedButtonDataflowService, OptimizedButtonDataflowService, ExtendedControlContext, } from "@/lib/services/optimizedButtonDataflowService"; import { dataflowJobQueue } from "@/lib/services/dataflowJobQueue"; import { cn } from "@/lib/utils"; import { Badge } from "@/components/ui/badge"; import { executeButtonWithFlow, handleFlowExecutionResult } from "@/lib/utils/nodeFlowButtonExecutor"; import { useCurrentFlowStep } from "@/stores/flowStepStore"; interface OptimizedButtonProps { component: ComponentData; onDataflowComplete?: (result: any) => void; onActionComplete?: (result: any) => void; formData?: Record; companyCode?: string; disabled?: boolean; selectedRows?: any[]; selectedRowsData?: any[]; flowSelectedData?: any[]; flowSelectedStepId?: number | null; // ๐Ÿ†• ํ…Œ์ด๋ธ” ์ „์ฒด ๋ฐ์ดํ„ฐ (table-all ๋ชจ๋“œ์šฉ) tableAllData?: any[]; // ๐Ÿ†• ํ”Œ๋กœ์šฐ ์Šคํ… ์ „์ฒด ๋ฐ์ดํ„ฐ (flow-step-all ๋ชจ๋“œ์šฉ) flowStepAllData?: any[]; // ๐Ÿ†• ํ…Œ์ด๋ธ” ์ „์ฒด ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์ฝœ๋ฐฑ (ํ•„์š” ์‹œ ๋ถ€๋ชจ์—์„œ ์ œ๊ณต) onRequestTableAllData?: () => Promise; // ๐Ÿ†• ํ”Œ๋กœ์šฐ ์Šคํ… ์ „์ฒด ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์ฝœ๋ฐฑ (ํ•„์š” ์‹œ ๋ถ€๋ชจ์—์„œ ์ œ๊ณต) onRequestFlowStepAllData?: (stepId: number) => Promise; } /** * ๐Ÿ”ฅ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋œ ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ * * ํ•ต์‹ฌ ๊ธฐ๋Šฅ: * 1. ์ฆ‰์‹œ ์‘๋‹ต (0-100ms) * 2. ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ œ์–ด๊ด€๋ฆฌ ์ฒ˜๋ฆฌ * 3. ์‹ค์‹œ๊ฐ„ ์ƒํƒœ ์ถ”์  * 4. ๋””๋ฐ”์šด์‹ฑ์œผ๋กœ ์ค‘๋ณต ํด๋ฆญ ๋ฐฉ์ง€ * 5. ์‹œ๊ฐ์  ํ”ผ๋“œ๋ฐฑ */ export const OptimizedButtonComponent: React.FC = ({ component, onDataflowComplete, onActionComplete, formData = {}, companyCode = "DEFAULT", disabled = false, selectedRows = [], selectedRowsData = [], flowSelectedData = [], flowSelectedStepId = null, tableAllData = [], flowStepAllData = [], onRequestTableAllData, onRequestFlowStepAllData, }) => { // ๐Ÿ”ฅ ์ƒํƒœ ๊ด€๋ฆฌ const [isExecuting, setIsExecuting] = useState(false); const [executionTime, setExecutionTime] = useState(null); const [backgroundJobs, setBackgroundJobs] = useState>(new Set()); const [lastResult, setLastResult] = useState(null); const [clickCount, setClickCount] = useState(0); const config = component.webTypeConfig; const buttonLabel = component.label || "๋ฒ„ํŠผ"; const flowConfig = config?.flowVisibilityConfig; // ๐Ÿ†• ํ˜„์žฌ ํ”Œ๋กœ์šฐ ๋‹จ๊ณ„ ๊ตฌ๋… const currentStep = useCurrentFlowStep(flowConfig?.targetFlowComponentId); // ๐Ÿ†• ๋ฒ„ํŠผ ํ‘œ์‹œ ์—ฌ๋ถ€ ๊ณ„์‚ฐ const shouldShowButton = useMemo(() => { // ํ”Œ๋กœ์šฐ ์ œ์–ด ๋น„ํ™œ์„ฑํ™” ์‹œ ํ•ญ์ƒ ํ‘œ์‹œ if (!flowConfig?.enabled) { return true; } // ํ”Œ๋กœ์šฐ ๋‹จ๊ณ„๊ฐ€ ์„ ํƒ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์ฒ˜๋ฆฌ if (currentStep === null) { // ๐Ÿ”ง ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ ๋ชจ๋“œ์ผ ๋•Œ๋Š” ๋‹จ๊ณ„ ๋ฏธ์„ ํƒ ์‹œ ์ˆจ๊น€ if (flowConfig.mode === "whitelist") { console.log("๐Ÿ” [OptimizedButton] ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ ๋ชจ๋“œ + ๋‹จ๊ณ„ ๋ฏธ์„ ํƒ โ†’ ์ˆจ๊น€"); return false; } // ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ๋‚˜ all ๋ชจ๋“œ๋Š” ํ‘œ์‹œ return true; } const { mode, visibleSteps = [], hiddenSteps = [] } = flowConfig; let result = true; if (mode === "whitelist") { result = visibleSteps.includes(currentStep); } else if (mode === "blacklist") { result = !hiddenSteps.includes(currentStep); } else if (mode === "all") { result = true; } // ํ•ญ์ƒ ๋กœ๊ทธ ์ถœ๋ ฅ (๊ฐœ๋ฐœ ๋ชจ๋“œ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ) console.log("๐Ÿ” [OptimizedButton] ํ‘œ์‹œ ์ฒดํฌ:", { buttonId: component.id, buttonLabel, flowComponentId: flowConfig.targetFlowComponentId, currentStep, mode, visibleSteps, hiddenSteps, result: result ? "ํ‘œ์‹œ โœ…" : "์ˆจ๊น€ โŒ", }); return result; }, [flowConfig, currentStep, component.id, buttonLabel]); // ๐Ÿ”ฅ ๋””๋ฐ”์šด์‹ฑ๋œ ํด๋ฆญ ํ•ธ๋“ค๋Ÿฌ (300ms) const handleClick = useCallback(async () => { if (isExecuting || disabled) return; // ํด๋ฆญ ์นด์šดํŠธ ์ฆ๊ฐ€ (ํ†ต๊ณ„์šฉ) setClickCount((prev) => prev + 1); setIsExecuting(true); const startTime = performance.now(); try { // console.log(`๐Ÿ”˜ Button clicked: ${component.id} (${config?.actionType})`); // ๐Ÿ”ฅ ํ™•์žฅ๋œ ์ปจํ…์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ const contextData = { ...formData, buttonId: component.id, componentData: component, timestamp: new Date().toISOString(), clickCount, }; // ๐Ÿ”ฅ ํ™•์žฅ๋œ ์ œ์–ด ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ const extendedContext = { formData, selectedRows: selectedRows || [], selectedRowsData: selectedRowsData || [], flowSelectedData: flowSelectedData || [], flowSelectedStepId: flowSelectedStepId, controlDataSource: config?.dataflowConfig?.controlDataSource || "form", buttonId: component.id, componentData: component, timestamp: new Date().toISOString(), clickCount, }; // ๐Ÿ”ฅ ์ œ์–ด ์ „์šฉ ์•ก์…˜์ธ์ง€ ํ™•์ธ const isControlOnlyAction = config?.actionType === "control"; // console.log("๐ŸŽฏ OptimizedButtonComponent ์‹คํ–‰:", { // actionType: config?.actionType, // isControlOnlyAction, // enableDataflowControl: config?.enableDataflowControl, // hasDataflowConfig: !!config?.dataflowConfig, // selectedRows, // selectedRowsData, // }); if (config?.enableDataflowControl && config?.dataflowConfig) { // ๐Ÿ†• ๋…ธ๋“œ ํ”Œ๋กœ์šฐ ๋ฐฉ์‹ ์‹คํ–‰ if (config.dataflowConfig.controlMode === "flow" && config.dataflowConfig.flowConfig) { console.log("๐Ÿ”„ ๋…ธ๋“œ ํ”Œ๋กœ์šฐ ๋ฐฉ์‹ ์‹คํ–‰:", config.dataflowConfig.flowConfig); console.log("๐Ÿ“Š ์ „๋‹ฌ๋  ๋ฐ์ดํ„ฐ ํ™•์ธ:", { controlDataSource: config.dataflowConfig.controlDataSource, formDataKeys: Object.keys(formData), selectedRowsDataLength: selectedRowsData.length, flowSelectedDataLength: flowSelectedData.length, flowSelectedStepId, }); // ๐Ÿ†• ๋ฐ์ดํ„ฐ ์†Œ์Šค์— ๋”ฐ๋ผ ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ ๋กœ๋“œ let preparedTableAllData = tableAllData; let preparedFlowStepAllData = flowStepAllData; const dataSource = config.dataflowConfig.controlDataSource; // table-all ๋ชจ๋“œ์ผ ๋•Œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ if (dataSource === "table-all" || dataSource === "all-sources") { if (tableAllData.length === 0 && onRequestTableAllData) { console.log("๐Ÿ“Š ํ…Œ์ด๋ธ” ์ „์ฒด ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์ค‘..."); try { preparedTableAllData = await onRequestTableAllData(); console.log(`โœ… ํ…Œ์ด๋ธ” ์ „์ฒด ๋ฐ์ดํ„ฐ ${preparedTableAllData.length}๊ฑด ๋กœ๋“œ ์™„๋ฃŒ`); } catch (error) { console.error("โŒ ํ…Œ์ด๋ธ” ์ „์ฒด ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ:", error); toast.error("ํ…Œ์ด๋ธ” ์ „์ฒด ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค"); } } } // flow-step-all ๋ชจ๋“œ์ผ ๋•Œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ if ((dataSource === "flow-step-all" || dataSource === "all-sources") && flowSelectedStepId) { if (flowStepAllData.length === 0 && onRequestFlowStepAllData) { console.log(`๐Ÿ“Š ํ”Œ๋กœ์šฐ ์Šคํ… ${flowSelectedStepId} ์ „์ฒด ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์ค‘...`); try { preparedFlowStepAllData = await onRequestFlowStepAllData(flowSelectedStepId); console.log(`โœ… ํ”Œ๋กœ์šฐ ์Šคํ… ์ „์ฒด ๋ฐ์ดํ„ฐ ${preparedFlowStepAllData.length}๊ฑด ๋กœ๋“œ ์™„๋ฃŒ`); } catch (error) { console.error("โŒ ํ”Œ๋กœ์šฐ ์Šคํ… ์ „์ฒด ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ:", error); toast.error("ํ”Œ๋กœ์šฐ ์Šคํ… ์ „์ฒด ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค"); } } } const flowResult = await executeButtonWithFlow( config.dataflowConfig.flowConfig, { buttonId: component.id, screenId: component.screenId, companyCode, userId: contextData.userId, formData, selectedRows: selectedRows || [], selectedRowsData: selectedRowsData || [], flowSelectedData: flowSelectedData || [], flowStepId: flowSelectedStepId || undefined, controlDataSource: config.dataflowConfig.controlDataSource, // ๐Ÿ†• ํ™•์žฅ๋œ ๋ฐ์ดํ„ฐ ์†Œ์Šค tableAllData: preparedTableAllData, flowStepAllData: preparedFlowStepAllData, }, // ์›๋ž˜ ์•ก์…˜ (timing์ด before๋‚˜ after์ผ ๋•Œ ์‹คํ–‰) async () => { if (!isControlOnlyAction) { await executeOriginalAction(config?.actionType || "save", contextData); } }, ); handleFlowExecutionResult(flowResult, { buttonId: component.id, formData, onRefresh: onDataflowComplete, }); if (onActionComplete) { onActionComplete(flowResult); } return; } // ๐Ÿ”ฅ ๊ธฐ์กด ๊ด€๊ณ„ ๋ฐฉ์‹ ์‹คํ–‰ const validationResult = await OptimizedButtonDataflowService.executeExtendedValidation( config.dataflowConfig, extendedContext as ExtendedControlContext, ); if (!validationResult.success) { toast.error(validationResult.message || "์ œ์–ด ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."); return; } // ๐Ÿ”ฅ ์ œ์–ด ์ „์šฉ ์•ก์…˜์ด๋ฉด ์—ฌ๊ธฐ์„œ ์ข…๋ฃŒ if (isControlOnlyAction) { toast.success("์ œ์–ด ์กฐ๊ฑด์„ ๋งŒ์กฑํ•ฉ๋‹ˆ๋‹ค."); if (onActionComplete) { onActionComplete({ success: true, message: "์ œ์–ด ์กฐ๊ฑด ํ†ต๊ณผ" }); } return; } // ๐Ÿ”ฅ ์ตœ์ ํ™”๋œ ๋ฒ„ํŠผ ์‹คํ–‰ (์ฆ‰์‹œ ์‘๋‹ต) await executeOptimizedButtonAction(contextData); } else if (isControlOnlyAction) { // ๐Ÿ”ฅ ์ œ์–ด๊ด€๋ฆฌ๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋œ ์ƒํƒœ์—์„œ ์ œ์–ด ์•ก์…˜ toast.warning( "์ œ์–ด๊ด€๋ฆฌ๋ฅผ ๋จผ์ € ํ™œ์„ฑํ™”ํ•ด์ฃผ์„ธ์š”. ์ œ์–ด ์•ก์…˜์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋ฒ„ํŠผ ์„ค์ •์—์„œ '์ œ์–ด๊ด€๋ฆฌ ํ™œ์„ฑํ™”'๋ฅผ ์ฒดํฌํ•˜๊ณ  ์กฐ๊ฑด์„ ์„ค์ •ํ•ด์ฃผ์„ธ์š”.", ); return; } else { // ๐Ÿ”ฅ ๊ธฐ์กด ์•ก์…˜๋งŒ ์‹คํ–‰ (์ œ์–ด ์•ก์…˜ ์ œ์™ธ) await executeOriginalAction(config?.actionType || "save", contextData); } } catch (error) { // console.error("Button execution failed:", error); toast.error("๋ฒ„ํŠผ ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); setLastResult({ success: false, error: error.message }); } finally { const endTime = performance.now(); const totalTime = endTime - startTime; setExecutionTime(totalTime); setIsExecuting(false); // ์„ฑ๋Šฅ ๋กœ๊น… if (totalTime > 200) { // console.warn(`๐ŸŒ Slow button execution: ${totalTime.toFixed(2)}ms`); } else { // console.log(`โšก Button execution: ${totalTime.toFixed(2)}ms`); } } }, [isExecuting, disabled, component.id, config?.actionType, config?.enableDataflowControl, formData, clickCount]); /** * ๐Ÿ”ฅ ์ตœ์ ํ™”๋œ ๋ฒ„ํŠผ ์•ก์…˜ ์‹คํ–‰ */ const executeOptimizedButtonAction = async (contextData: Record) => { const actionType = config?.actionType as ButtonActionType; if (!actionType) { throw new Error("์•ก์…˜ ํƒ€์ž…์ด ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."); } // ๐Ÿ”ฅ API ํ˜ธ์ถœ (์ฆ‰์‹œ ์‘๋‹ต) const result = await optimizedButtonDataflowService.executeButtonWithDataflow( component.id, actionType, config, contextData, companyCode, ); const { jobId, immediateResult, isBackground, timing } = result; // ๐Ÿ”ฅ ์ฆ‰์‹œ ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ if (immediateResult) { handleImmediateResult(actionType, immediateResult); setLastResult(immediateResult); // ์‚ฌ์šฉ์ž์—๊ฒŒ ์ฆ‰์‹œ ํ”ผ๋“œ๋ฐฑ const message = getSuccessMessage(actionType, timing); if (immediateResult.success) { toast.success(message); } else { toast.error(immediateResult.message || "์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); } // ์ฝœ๋ฐฑ ํ˜ธ์ถœ if (onActionComplete) { onActionComplete(immediateResult); } } // ๐Ÿ”ฅ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ์ถ”์  if (isBackground && jobId && jobId !== "immediate") { setBackgroundJobs((prev) => new Set([...prev, jobId])); // ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ์™„๋ฃŒ ๋Œ€๊ธฐ (์„ ํƒ์ ) if (timing === "before") { // before ํƒ€์ด๋ฐ์€ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋‹ค๋ ค์•ผ ํ•จ await waitForBackgroundJob(jobId); } else { // after/replace ํƒ€์ด๋ฐ์€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์กฐ์šฉํžˆ ์ฒ˜๋ฆฌ trackBackgroundJob(jobId); } } }; /** * ๐Ÿ”ฅ ์ฆ‰์‹œ ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ */ const handleImmediateResult = (actionType: ButtonActionType, result: any) => { if (!result.success) return; switch (actionType) { case "save": // console.log("๐Ÿ’พ Save action completed:", result); break; case "delete": // console.log("๐Ÿ—‘๏ธ Delete action completed:", result); break; case "search": // console.log("๐Ÿ” Search action completed:", result); break; case "add": // console.log("โž• Add action completed:", result); break; case "edit": // console.log("โœ๏ธ Edit action completed:", result); break; default: // console.log(`โœ… ${actionType} action completed:`, result); } }; /** * ๐Ÿ”ฅ ์„ฑ๊ณต ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ */ const getSuccessMessage = (actionType: ButtonActionType, timing?: string): string => { const actionName = getActionDisplayName(actionType); switch (timing) { case "before": return `${actionName} ์ž‘์—…์„ ์ฒ˜๋ฆฌ ์ค‘์ž…๋‹ˆ๋‹ค...`; case "after": return `${actionName}์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`; case "replace": return `์‚ฌ์šฉ์ž ์ •์˜ ์ž‘์—…์„ ์ฒ˜๋ฆฌ ์ค‘์ž…๋‹ˆ๋‹ค...`; default: return `${actionName}์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`; } }; /** * ๐Ÿ”ฅ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ์ถ”์  (polling ๋ฐฉ์‹) */ const trackBackgroundJob = (jobId: string) => { const pollInterval = 1000; // 1์ดˆ let pollCount = 0; const maxPolls = 60; // ์ตœ๋Œ€ 1๋ถ„ const pollJobStatus = async () => { pollCount++; try { const status = optimizedButtonDataflowService.getJobStatus(jobId); if (status.status === "completed") { setBackgroundJobs((prev) => { const newSet = new Set(prev); newSet.delete(jobId); return newSet; }); // ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ์™„๋ฃŒ ์•Œ๋ฆผ (์กฐ์šฉํ•˜๊ฒŒ) if (status.result?.executedActions > 0) { toast.success(`์ถ”๊ฐ€ ์ฒ˜๋ฆฌ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. (${status.result.executedActions}๊ฐœ ์•ก์…˜)`, { duration: 2000 }); } if (onDataflowComplete) { onDataflowComplete(status.result); } return; } if (status.status === "failed") { setBackgroundJobs((prev) => { const newSet = new Set(prev); newSet.delete(jobId); return newSet; }); // console.error("Background job failed:", status.result); toast.error("๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", { duration: 3000 }); return; } // ์•„์ง ์ง„ํ–‰ ์ค‘์ด๊ณ  ์ตœ๋Œ€ ํšŸ์ˆ˜ ๋ฏธ๋‹ฌ ์‹œ ๊ณ„์† polling if (pollCount < maxPolls && (status.status === "pending" || status.status === "processing")) { setTimeout(pollJobStatus, pollInterval); } else if (pollCount >= maxPolls) { // console.warn(`Background job polling timeout: ${jobId}`); setBackgroundJobs((prev) => { const newSet = new Set(prev); newSet.delete(jobId); return newSet; }); } } catch (error) { // console.error("Failed to check job status:", error); setBackgroundJobs((prev) => { const newSet = new Set(prev); newSet.delete(jobId); return newSet; }); } }; // ์ฒซ polling ์‹œ์ž‘ setTimeout(pollJobStatus, 500); }; /** * ๐Ÿ”ฅ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ์™„๋ฃŒ ๋Œ€๊ธฐ (before ํƒ€์ด๋ฐ์šฉ) */ const waitForBackgroundJob = async (jobId: string): Promise => { return new Promise((resolve, reject) => { const maxWaitTime = 30000; // ์ตœ๋Œ€ 30์ดˆ ๋Œ€๊ธฐ const pollInterval = 500; // 0.5์ดˆ let elapsedTime = 0; const checkStatus = async () => { try { const status = optimizedButtonDataflowService.getJobStatus(jobId); if (status.status === "completed") { setBackgroundJobs((prev) => { const newSet = new Set(prev); newSet.delete(jobId); return newSet; }); toast.success("๋ชจ๋“  ์ฒ˜๋ฆฌ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); if (onDataflowComplete) { onDataflowComplete(status.result); } resolve(); return; } if (status.status === "failed") { setBackgroundJobs((prev) => { const newSet = new Set(prev); newSet.delete(jobId); return newSet; }); toast.error("์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); reject(new Error(status.result?.error || "Unknown error")); return; } // ์‹œ๊ฐ„ ์ฒดํฌ elapsedTime += pollInterval; if (elapsedTime >= maxWaitTime) { reject(new Error("Processing timeout")); return; } // ๊ณ„์† ๋Œ€๊ธฐ setTimeout(checkStatus, pollInterval); } catch (error) { reject(error); } }; checkStatus(); }); }; /** * ๐Ÿ”ฅ ๊ธฐ์กด ์•ก์…˜ ์‹คํ–‰ (์ œ์–ด๊ด€๋ฆฌ ์—†์Œ) */ const executeOriginalAction = async ( actionType: ButtonActionType, contextData: Record, ): Promise => { // ๐Ÿ”ฅ ์ œ์–ด ์•ก์…˜์€ ์—ฌ๊ธฐ์„œ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์Œ (์ด๋ฏธ ์œ„์—์„œ ์ฒ˜๋ฆฌ๋จ) if (actionType === "control") { // console.warn("์ œ์–ด ์•ก์…˜์€ executeOriginalAction์—์„œ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค."); return; } // ๊ฐ„๋‹จํ•œ mock ์ฒ˜๋ฆฌ (์‹ค์ œ๋กœ๋Š” API ํ˜ธ์ถœ) await new Promise((resolve) => setTimeout(resolve, 100)); // 100ms ์‹œ๋ฎฌ๋ ˆ์ด์…˜ const result = { success: true, message: `${getActionDisplayName(actionType)}์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`, actionType, timestamp: new Date().toISOString(), }; setLastResult(result); toast.success(result.message); if (onActionComplete) { onActionComplete(result); } return result; }; /** * ์•ก์…˜ ํƒ€์ž…๋ณ„ ํ‘œ์‹œ๋ช… */ const getActionDisplayName = (actionType: ButtonActionType): string => { const displayNames: Record = { save: "์ €์žฅ", delete: "์‚ญ์ œ", edit: "์ˆ˜์ •", add: "์ถ”๊ฐ€", search: "๊ฒ€์ƒ‰", reset: "์ดˆ๊ธฐํ™”", submit: "์ œ์ถœ", close: "๋‹ซ๊ธฐ", popup: "ํŒ์—…", modal: "๋ชจ๋‹ฌ", newWindow: "์ƒˆ ์ฐฝ", navigate: "ํŽ˜์ด์ง€ ์ด๋™", control: "์ œ์–ด", }; return displayNames[actionType] || actionType; }; /** * ๋ฒ„ํŠผ ์ƒํƒœ์— ๋”ฐ๋ฅธ ์•„์ด์ฝ˜ */ const getStatusIcon = () => { if (isExecuting) { return ; } if (lastResult?.success === false) { return ; } if (lastResult?.success === true) { return ; } return null; }; /** * ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ์ƒํƒœ ํ‘œ์‹œ */ const renderBackgroundStatus = () => { if (backgroundJobs.size === 0) return null; return (
{backgroundJobs.size}
); }; // ๐Ÿ†• ํ”Œ๋กœ์šฐ ๋‹จ๊ณ„๋ณ„ ํ‘œ์‹œ ์ œ์–ด if (!shouldShowButton) { // ๋ ˆ์ด์•„์›ƒ ๋™์ž‘์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒŒ ์ฒ˜๋ฆฌ if (flowConfig?.layoutBehavior === "preserve-position") { // ์œ„์น˜ ์œ ์ง€ (๋นˆ ๊ณต๊ฐ„, display: none) return
; } else { // ์™„์ „ํžˆ ๋ Œ๋”๋งํ•˜์ง€ ์•Š์Œ (auto-compact, ๋นˆ ๊ณต๊ฐ„ ์ œ๊ฑฐ) return null; } } return (
{/* ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ์ƒํƒœ ํ‘œ์‹œ */} {renderBackgroundStatus()} {/* ๐Ÿ†• ํ”Œ๋กœ์šฐ ์ œ์–ด ํ™œ์„ฑํ™” ํ‘œ์‹œ */} {flowConfig?.enabled && (
)} {/* ์ œ์–ด๊ด€๋ฆฌ ํ™œ์„ฑํ™” ํ‘œ์‹œ */} {config?.enableDataflowControl && (
๐Ÿ”ง
)}
); }; export default OptimizedButtonComponent;