250 lines
8.4 KiB
TypeScript
250 lines
8.4 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 '../shared/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';
|
|
|
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
|
|
|
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 { t } = useLanguage();
|
|
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="">{t('MIME-Typ 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="">{t('Wählen')}</option>
|
|
<option value="true">{t('Ja (true)')}</option>
|
|
<option value="false">{t('Nein (false)')}</option>
|
|
</select>
|
|
);
|
|
}
|
|
return (
|
|
<input
|
|
type="text"
|
|
className={styles.startsInput}
|
|
value={valStr}
|
|
onChange={(e) => handleCaseValueChange(index, e.target.value)}
|
|
placeholder={isMimeTypeRef ? t('z.B. application/pdf') : t('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>{t('Datenquelle')}</label>
|
|
<RefSourceSelect
|
|
value={ref}
|
|
onChange={handleRefChange}
|
|
placeholder={t('Feld zum Vergleich wählen')}
|
|
/>
|
|
</div>
|
|
|
|
{!ref && (
|
|
<div className={styles.ifElseConditionRow}>
|
|
<label>{t('Fester Wert (ohne Referenz)')}</label>
|
|
<input
|
|
type="text"
|
|
value={String(staticValue ?? '')}
|
|
onChange={(e) => handleStaticValueChange(e.target.value)}
|
|
placeholder={t('z. B. CH oder 42')}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<div className={styles.ifElseConditionRow}>
|
|
<label>{t('Fälle / Reihenfolge / Ausgabe')}</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}>
|
|
{t('+ Fall')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|