/** * Loop node - Datenquelle für Iteration mit benutzerfreundlichen Labels. * Zeigt nur iterierbare Quellen: Arrays und Objekte (Formularfelder → {name, value}). */ import React from 'react'; import { createRef, isRef, type DataRef } from './dataRef'; import { refToOptionValue, optionValueToRef } from './RefSourceSelect'; import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext'; import styles from '../../editor/Automation2FlowEditor.module.css'; import { useLanguage } from '../../../../providers/language/LanguageContext'; interface LoopOption { ref: DataRef; label: string; } function getValueAtPath(obj: unknown, path: (string | number)[]): unknown { let current: unknown = obj; for (const seg of path) { if (current == null) return undefined; const key = typeof seg === 'number' ? String(seg) : seg; if (Array.isArray(current) && /^\d+$/.test(key)) { current = current[parseInt(key, 10)]; } else if (typeof current === 'object' && key in (current as object)) { current = (current as Record)[key]; } else return undefined; } return current; } /** Build iterable options with friendly labels for Loop node */ function buildLoopOptions( sourceIds: string[], nodes: Array<{ id: string; type?: string; title?: string; parameters?: Record }>, nodeOutputsPreview: Record, getNodeLabel: (n: { id: string; type?: string; title?: string }) => string ): LoopOption[] { const options: LoopOption[] = []; for (const nodeId of sourceIds) { const node = nodes.find((n) => n.id === nodeId); if (node?.type === 'trigger.manual') continue; const nodeLabel = getNodeLabel(node ?? { id: nodeId }); const preview = nodeOutputsPreview[nodeId]; // Special cases with friendly labels if (node?.type === 'trigger.form') { options.push({ ref: createRef(nodeId, ['payload']), label: `Alle Formularfelder (${nodeLabel})`, }); const filesVal = getValueAtPath(preview, ['files']); if (Array.isArray(filesVal)) { options.push({ ref: createRef(nodeId, ['files']), label: `Alle Dateien aus Formular (${nodeLabel})`, }); } continue; } if (node?.type === 'input.form') { options.push({ ref: createRef(nodeId, []), label: `Alle Formularfelder (${nodeLabel})`, }); continue; } if (node?.type === 'input.upload') { options.push({ ref: createRef(nodeId, ['files']), label: `Alle hochgeladenen Dateien (${nodeLabel})`, }); options.push({ ref: createRef(nodeId, ['fileIds']), label: `Alle Datei-IDs (${nodeLabel})`, }); continue; } if (node?.type === 'flow.loop') { options.push({ ref: createRef(nodeId, ['items']), label: `Alle Elemente aus Schleife (${nodeLabel})`, }); continue; } if (node?.type === 'email.searchEmail') { options.push({ ref: createRef(nodeId, ['data', 'searchResults', 'results']), label: `Alle gefundenen E-Mails (${nodeLabel})`, }); continue; } if (node?.type === 'email.checkEmail') { options.push({ ref: createRef(nodeId, ['data', 'emails', 'emails']), label: `Alle E-Mails (${nodeLabel})`, }); continue; } if (node?.type === 'sharepoint.listFiles') { options.push({ ref: createRef(nodeId, ['files']), label: `Alle Dateien (${nodeLabel})`, }); continue; } // Generic: find top-level arrays and root object in preview if (preview != null && typeof preview === 'object') { for (const [k, v] of Object.entries(preview as Record)) { const path: (string | number)[] = [k]; const pathStr = path.join('.'); if (Array.isArray(v)) { options.push({ ref: createRef(nodeId, path), label: `${nodeLabel}.${pathStr}`, }); } else if (v != null && typeof v === 'object' && !Array.isArray(v)) { const inner = v as Record; for (const [k2, v2] of Object.entries(inner)) { if (Array.isArray(v2)) { options.push({ ref: createRef(nodeId, [k, k2]), label: `${nodeLabel}.${k}.${k2}`, }); } } } } } } // Deduplicate by ref (path might repeat from different collection) const seen = new Set(); return options.filter((o) => { const key = refToOptionValue(o.ref); if (seen.has(key)) return false; seen.add(key); return true; }); } interface LoopItemsSelectProps { value: DataRef | { type: 'value'; value: unknown } | null; onChange: (ref: DataRef | null) => void; placeholder?: string; } export const LoopItemsSelect: React.FC = ({ value, onChange, placeholder = 'Über was soll iteriert werden?', }) => { const { t } = useLanguage(); const dataFlow = useAutomation2DataFlow(); if (!dataFlow) return null; const sourceIds = dataFlow.getAvailableSourceIds(); if (sourceIds.length === 0) { return (

Keine vorherigen Nodes verbunden. Verbinden Sie zuerst Nodes mit der Schleife.

); } const options = buildLoopOptions( sourceIds, dataFlow.nodes, dataFlow.nodeOutputsPreview, dataFlow.getNodeLabel ); const ref = isRef(value) ? value : null; const currentValue = ref ? refToOptionValue(ref) : ''; return (

Z.B. für jedes Formularfeld, jede Datei aus Upload, jede E-Mail aus Suche.

); };