ui-nyla/src/components/Automation2FlowEditor/nodes/switch/SwitchNodeConfig.tsx

247 lines
8.2 KiB
TypeScript

/**
* Switch node config - RefSourceSelect für Datenquelle, Fälle mit Operator + Wert.
* Gleicher Kontext wie IfElse: typabhängige Operatoren (z.B. Alter < 19, = 30).
*/
import React from 'react';
import type { NodeConfigRendererProps } from '../configs/types';
import { RefSourceSelect, getFieldType } from '../shared/RefSourceSelect';
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
import { isRef, createValue } from '../shared/dataRef';
import { getMimeTypeOptionsFromUploadParams } from '../runtime/fileTypeMimeMapping';
import { operatorsForType } from '../shared/conditionOperators';
import styles from '../../editor/Automation2FlowEditor.module.css';
export interface SwitchCase {
operator: string;
value?: string | number | boolean;
}
function normalizeCase(c: unknown): SwitchCase {
if (c && typeof c === 'object' && 'operator' in (c as object)) {
const o = c as SwitchCase;
const v = o.value;
const safeValue: string | number | boolean | undefined =
typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' ? v : undefined;
return { operator: o.operator ?? 'eq', value: safeValue };
}
const fallbackValue: string | number | boolean | undefined =
typeof c === 'string' || typeof c === 'number' || typeof c === 'boolean' ? c : undefined;
return { operator: 'eq', value: fallbackValue };
}
export const SwitchNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, updateParam }) => {
const dataFlow = useAutomation2DataFlow();
const valueParam = params.value;
const ref = isRef(valueParam) ? valueParam : null;
let staticValue: string | number = '';
if (!ref && valueParam != null) {
if (typeof valueParam === 'object' && 'value' in valueParam) {
const v = (valueParam as { value: unknown }).value;
staticValue = v !== undefined && v !== null ? String(v) : '';
} else if (typeof valueParam === 'string' || typeof valueParam === 'number') {
staticValue = valueParam;
}
}
const rawCases = (params.cases as unknown[]) ?? [];
const cases: SwitchCase[] = rawCases.map(normalizeCase);
const fieldType = dataFlow ? getFieldType(ref, dataFlow.nodes, dataFlow.nodeOutputsPreview) : 'unknown';
const operators = operatorsForType(fieldType);
const isMimeTypeRef =
ref && ref.path?.length >= 2 && ref.path[ref.path.length - 1] === 'mimeType';
const sourceNode = ref && dataFlow
? dataFlow.nodes.find((n: { id: string; type?: string; parameters?: Record<string, unknown> }) => n.id === ref.nodeId)
: null;
const mimeTypeOptions =
isMimeTypeRef && sourceNode?.type === 'input.upload' && sourceNode.parameters
? getMimeTypeOptionsFromUploadParams(sourceNode.parameters as Record<string, unknown>)
: [];
const setValue = (val: unknown) => {
updateParam('value', val);
};
const setCases = (next: SwitchCase[]) => {
updateParam('cases', next);
};
const handleRefChange = (newRef: { type: 'ref'; nodeId: string; path: (string | number)[] } | null) => {
if (newRef) {
setValue(newRef);
} else {
setValue(createValue(staticValue));
}
};
const handleStaticValueChange = (v: string) => {
setValue(createValue(fieldType === 'number' ? parseFloat(v) || 0 : v));
};
const handleCaseOperatorChange = (index: number, op: string) => {
const opDef = operators.find((o) => o.value === op);
const next = [...cases];
next[index] = {
operator: op,
value: opDef?.needsValue ? cases[index]?.value : undefined,
};
setCases(next);
};
const handleCaseValueChange = (index: number, v: string | number | boolean) => {
const next = [...cases];
next[index] = {
...next[index],
value: fieldType === 'number' ? (typeof v === 'number' ? v : parseFloat(String(v)) || 0)
: fieldType === 'boolean' ? (v === true || v === 'true')
: String(v),
};
setCases(next);
};
const renderCaseValueInput = (caseItem: SwitchCase, index: number) => {
const val = caseItem.value;
const valStr = String(val ?? '');
if (mimeTypeOptions.length > 0) {
return (
<select
value={valStr}
onChange={(e) => handleCaseValueChange(index, e.target.value)}
className={styles.startsInput}
>
<option value=""> MIME-Type wählen </option>
{mimeTypeOptions.map((o) => (
<option key={o.value} value={o.value}>
{o.label} ({o.value})
</option>
))}
</select>
);
}
if (fieldType === 'number') {
return (
<input
type="number"
className={styles.startsInput}
value={valStr}
onChange={(e) => handleCaseValueChange(index, parseFloat(e.target.value) || 0)}
placeholder="0"
/>
);
}
if (fieldType === 'date') {
return (
<input
type="date"
className={styles.startsInput}
value={valStr}
onChange={(e) => handleCaseValueChange(index, e.target.value)}
/>
);
}
if (fieldType === 'boolean') {
return (
<select
value={val === true ? 'true' : val === false ? 'false' : ''}
onChange={(e) => {
const v = e.target.value;
handleCaseValueChange(index, v === 'true' ? true : v === 'false' ? false : '');
}}
className={styles.startsInput}
>
<option value=""> wählen </option>
<option value="true">Ja / wahr</option>
<option value="false">Nein / falsch</option>
</select>
);
}
return (
<input
type="text"
className={styles.startsInput}
value={valStr}
onChange={(e) => handleCaseValueChange(index, e.target.value)}
placeholder={isMimeTypeRef ? 'z.B. application/pdf' : `Wert`}
/>
);
};
const addCase = () => {
const opDef = operators[0];
const defaultVal = opDef?.needsValue
? (fieldType === 'number' ? 0 : fieldType === 'boolean' ? false : '')
: undefined;
setCases([
...cases,
{ operator: opDef?.value ?? 'eq', value: defaultVal },
]);
};
return (
<div className={styles.ifElseConditionEditor}>
<div className={styles.ifElseConditionRow}>
<label>Datenquelle</label>
<RefSourceSelect
value={ref}
onChange={handleRefChange}
placeholder="Feld zum Vergleichen wählen…"
/>
</div>
{!ref && (
<div className={styles.ifElseConditionRow}>
<label>Fester Wert (falls keine Referenz)</label>
<input
type="text"
value={String(staticValue ?? '')}
onChange={(e) => handleStaticValueChange(e.target.value)}
placeholder="z.B. CH oder 42"
/>
</div>
)}
<div className={styles.ifElseConditionRow}>
<label>Fälle (Reihenfolge = Ausgang)</label>
<div className={styles.formFieldsList}>
{cases.map((c, i) => {
const opDef = operators.find((o) => o.value === c.operator) ?? operators[0];
const needsValue = opDef?.needsValue ?? true;
return (
<div key={i} className={styles.formFieldRow}>
<select
value={c.operator}
onChange={(e) => handleCaseOperatorChange(i, e.target.value)}
className={styles.startsInput}
style={{ minWidth: 140 }}
>
{operators.map((o) => (
<option key={o.value} value={o.value}>
{o.label}
</option>
))}
</select>
{needsValue && (
<div style={{ flex: 1 }}>
{renderCaseValueInput(c, i)}
</div>
)}
<button
type="button"
className={styles.formFieldRemoveButton}
onClick={() => setCases(cases.filter((_, j) => j !== i))}
>
</button>
</div>
);
})}
<button type="button" className={styles.startsAddBtn} onClick={addCase}>
+ Fall
</button>
</div>
</div>
</div>
);
};