엑셀 업로드 제어로직 설정 가능하도록 수정

This commit is contained in:
kjs
2026-01-09 15:46:09 +09:00
parent aa0698556e
commit ba2a281245
5 changed files with 275 additions and 10 deletions

View File

@@ -3281,10 +3281,12 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
)}
</div>
{/* 제어 기능 섹션 */}
<div className="border-border mt-8 border-t pt-6">
<ImprovedButtonControlConfigPanel component={component} onUpdateProperty={onUpdateProperty} />
</div>
{/* 제어 기능 섹션 - 엑셀 업로드가 아닐 때만 표시 */}
{(component.componentConfig?.action?.type || "save") !== "excel_upload" && (
<div className="border-border mt-8 border-t pt-6">
<ImprovedButtonControlConfigPanel component={component} onUpdateProperty={onUpdateProperty} />
</div>
)}
{/* 🆕 플로우 단계별 표시 제어 섹션 (플로우 위젯이 있을 때만 표시) */}
{hasFlowWidget && (
@@ -3724,12 +3726,189 @@ const MasterDetailExcelUploadConfig: React.FC<{
<p className="text-muted-foreground text-xs"> .</p>
</div>
)}
{/* 업로드 후 제어 실행 설정 */}
<AfterUploadControlConfig
config={config}
onUpdateProperty={onUpdateProperty}
masterDetailConfig={masterDetailConfig}
updateMasterDetailConfig={updateMasterDetailConfig}
/>
</div>
)}
</div>
);
};
/**
* 업로드 후 제어 실행 설정 컴포넌트
* 여러 개의 제어를 순서대로 실행할 수 있도록 지원
*/
const AfterUploadControlConfig: React.FC<{
config: any;
onUpdateProperty: (path: string, value: any) => void;
masterDetailConfig: any;
updateMasterDetailConfig: (updates: any) => void;
}> = ({ masterDetailConfig, updateMasterDetailConfig }) => {
const [nodeFlows, setNodeFlows] = useState<
Array<{ flowId: number; flowName: string; flowDescription?: string }>
>([]);
const [flowSelectOpen, setFlowSelectOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
// 선택된 제어 목록 (배열로 관리)
const selectedFlows: Array<{ flowId: string; order: number }> = masterDetailConfig.afterUploadFlows || [];
// 노드 플로우 목록 로드
useEffect(() => {
const loadNodeFlows = async () => {
setIsLoading(true);
try {
const { apiClient } = await import("@/lib/api/client");
const response = await apiClient.get("/dataflow/node-flows");
if (response.data?.success && response.data?.data) {
setNodeFlows(response.data.data);
}
} catch (error) {
console.error("노드 플로우 목록 로드 실패:", error);
} finally {
setIsLoading(false);
}
};
loadNodeFlows();
}, []);
// 제어 추가
const addFlow = (flowId: string) => {
if (selectedFlows.some((f) => f.flowId === flowId)) return;
const newFlows = [...selectedFlows, { flowId, order: selectedFlows.length + 1 }];
updateMasterDetailConfig({ afterUploadFlows: newFlows });
setFlowSelectOpen(false);
};
// 제어 제거
const removeFlow = (flowId: string) => {
const newFlows = selectedFlows
.filter((f) => f.flowId !== flowId)
.map((f, idx) => ({ ...f, order: idx + 1 }));
updateMasterDetailConfig({ afterUploadFlows: newFlows });
};
// 순서 변경 (위로)
const moveUp = (index: number) => {
if (index === 0) return;
const newFlows = [...selectedFlows];
[newFlows[index - 1], newFlows[index]] = [newFlows[index], newFlows[index - 1]];
updateMasterDetailConfig({
afterUploadFlows: newFlows.map((f, idx) => ({ ...f, order: idx + 1 })),
});
};
// 순서 변경 (아래로)
const moveDown = (index: number) => {
if (index === selectedFlows.length - 1) return;
const newFlows = [...selectedFlows];
[newFlows[index], newFlows[index + 1]] = [newFlows[index + 1], newFlows[index]];
updateMasterDetailConfig({
afterUploadFlows: newFlows.map((f, idx) => ({ ...f, order: idx + 1 })),
});
};
// 선택되지 않은 플로우만 필터링
const availableFlows = nodeFlows.filter((f) => !selectedFlows.some((s) => s.flowId === String(f.flowId)));
return (
<div className="border-t pt-3">
<Label className="text-xs"> </Label>
<p className="text-muted-foreground mb-2 text-xs">
.
</p>
{/* 선택된 제어 목록 */}
{selectedFlows.length > 0 && (
<div className="mb-2 space-y-1">
{selectedFlows.map((selected, index) => {
const flow = nodeFlows.find((f) => String(f.flowId) === selected.flowId);
return (
<div key={selected.flowId} className="flex items-center gap-1 rounded border bg-white p-1.5">
<span className="text-muted-foreground w-5 text-center text-xs">{index + 1}</span>
<span className="flex-1 truncate text-xs">{flow?.flowName || `Flow ${selected.flowId}`}</span>
<Button
variant="ghost"
size="sm"
className="h-5 w-5 p-0"
onClick={() => moveUp(index)}
disabled={index === 0}
>
<ChevronUp className="h-3 w-3" />
</Button>
<Button
variant="ghost"
size="sm"
className="h-5 w-5 p-0"
onClick={() => moveDown(index)}
disabled={index === selectedFlows.length - 1}
>
<ChevronDown className="h-3 w-3" />
</Button>
<Button variant="ghost" size="sm" className="h-5 w-5 p-0 text-red-500" onClick={() => removeFlow(selected.flowId)}>
<X className="h-3 w-3" />
</Button>
</div>
);
})}
</div>
)}
{/* 제어 추가 버튼 */}
<Popover open={flowSelectOpen} onOpenChange={setFlowSelectOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
className="h-8 w-full justify-between text-xs"
disabled={isLoading || availableFlows.length === 0}
>
{isLoading ? "로딩 중..." : availableFlows.length === 0 ? "추가 가능한 제어 없음" : "제어 추가..."}
<Plus className="ml-2 h-4 w-4" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px] p-0" align="start">
<Command>
<CommandInput placeholder="제어 검색..." className="text-xs" />
<CommandList>
<CommandEmpty className="py-2 text-center text-xs"> </CommandEmpty>
<CommandGroup>
{availableFlows.map((flow) => (
<CommandItem
key={flow.flowId}
value={flow.flowName}
onSelect={() => addFlow(String(flow.flowId))}
className="text-xs"
>
<div className="flex flex-col">
<span>{flow.flowName}</span>
{flow.flowDescription && (
<span className="text-muted-foreground text-[10px]">{flow.flowDescription}</span>
)}
</div>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
{selectedFlows.length > 0 && (
<p className="text-muted-foreground mt-1 text-xs">
{selectedFlows.length} .
</p>
)}
</div>
);
};
/**
* 엑셀 업로드 설정 섹션 컴포넌트
* 마스터-디테일 설정은 분할 패널 자동 감지