128 lines
4.9 KiB
TypeScript
128 lines
4.9 KiB
TypeScript
/**
|
|
* Start node (Formular) — define fields that appear at run time (payload.*).
|
|
*/
|
|
|
|
import React, { useMemo } from 'react';
|
|
import type { NodeConfigRendererProps } from '../shared/types';
|
|
import type { FormField } from '../shared/types';
|
|
import { FORM_FIELD_TYPES, FORM_FIELD_TYPE_LABELS } from '../../../../utils/attributeTypeMapper';
|
|
import styles from '../../editor/Automation2FlowEditor.module.css';
|
|
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
|
import { FormFieldOptionsEditor } from '../form/FormFieldOptionsEditor';
|
|
import {
|
|
deriveFormFieldPayloadKey,
|
|
formFieldTypeHasConfigurableOptions,
|
|
normalizeFormFieldOptions,
|
|
} from '../form/formFieldOptionsUtils';
|
|
|
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
|
|
|
function _parseFields(params: Record<string, unknown>, t: (key: string) => string): FormField[] {
|
|
const raw = params.formFields;
|
|
if (!Array.isArray(raw)) return [{ name: 'field1', label: t('Feld 1'), type: 'text' }];
|
|
return raw.map((f, i) => {
|
|
if (f && typeof f === 'object' && !Array.isArray(f)) {
|
|
const o = f as Record<string, unknown>;
|
|
const rawType = String(o.type ?? 'text');
|
|
const name = String(o.name ?? `field${i + 1}`);
|
|
const label = String(o.label ?? `${t('Feld')} ${i + 1}`);
|
|
const type = (FORM_FIELD_TYPES as readonly string[]).includes(rawType) ? rawType : 'text';
|
|
const required = Boolean(o.required);
|
|
const options = formFieldTypeHasConfigurableOptions(type) ? normalizeFormFieldOptions(o.options) : undefined;
|
|
return { name, label, type, required, ...(options !== undefined ? { options } : {}) } as FormField;
|
|
}
|
|
return { name: `field${i + 1}`, label: `${t('Feld')} ${i + 1}`, type: 'text' as const };
|
|
});
|
|
}
|
|
|
|
export const FormStartNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, updateParam }) => {
|
|
const { t } = useLanguage();
|
|
const ctx = useAutomation2DataFlow();
|
|
const fieldTypeOptions = ctx?.formFieldTypes?.length
|
|
? ctx.formFieldTypes
|
|
: FORM_FIELD_TYPES.map((ft) => ({ id: ft, label: FORM_FIELD_TYPE_LABELS[ft] ?? ft, portType: 'str' }));
|
|
const fields = useMemo(() => _parseFields(params, t), [params, t]);
|
|
|
|
const setFields = (next: FormField[]) => {
|
|
updateParam('formFields', next);
|
|
};
|
|
|
|
return (
|
|
<div className={styles.startNodeDoc}>
|
|
<p className={styles.startNodeDocIntro}>
|
|
<strong>{t('Formular-Felder')}</strong>{' '}
|
|
{t('werden beim Start ausgefüllt. Der Payload-Schlüssel wird aus der Beschriftung abgeleitet.')}
|
|
</p>
|
|
<div className={styles.formFieldsList}>
|
|
{fields.map((f, idx) => (
|
|
<div key={idx} className={styles.formFieldRow}>
|
|
<input
|
|
className={styles.startsInput}
|
|
placeholder={t('Beschriftung')}
|
|
value={f.label ?? ''}
|
|
onChange={(e) => {
|
|
const label = e.target.value;
|
|
const next = [...fields];
|
|
next[idx] = { ...f, label, name: deriveFormFieldPayloadKey(label, idx) };
|
|
setFields(next);
|
|
}}
|
|
/>
|
|
<select
|
|
className={styles.startsSelect}
|
|
value={f.type ?? 'text'}
|
|
onChange={(e) => {
|
|
const next = [...fields];
|
|
const type = e.target.value as FormField['type'];
|
|
const row: FormField = { ...f, type };
|
|
if (formFieldTypeHasConfigurableOptions(type)) {
|
|
row.options = normalizeFormFieldOptions(row.options);
|
|
}
|
|
next[idx] = row;
|
|
setFields(next);
|
|
}}
|
|
>
|
|
{fieldTypeOptions.map((ft) => (
|
|
<option key={ft.id} value={ft.id}>{t(ft.label)}</option>
|
|
))}
|
|
</select>
|
|
<button
|
|
type="button"
|
|
className={styles.formFieldRemoveButton}
|
|
onClick={() => setFields(fields.filter((_, j) => j !== idx))}
|
|
>
|
|
✕
|
|
</button>
|
|
{formFieldTypeHasConfigurableOptions(f.type) ? (
|
|
<div className={styles.formFieldOptionsBlock}>
|
|
<FormFieldOptionsEditor
|
|
options={normalizeFormFieldOptions(f.options)}
|
|
onChange={(opts) => {
|
|
const next = [...fields];
|
|
next[idx] = { ...next[idx], options: opts };
|
|
setFields(next);
|
|
}}
|
|
/>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
))}
|
|
<button
|
|
type="button"
|
|
className={styles.startsAddBtn}
|
|
onClick={() =>
|
|
setFields([
|
|
...fields,
|
|
{
|
|
name: deriveFormFieldPayloadKey('', fields.length),
|
|
label: '',
|
|
type: 'text',
|
|
},
|
|
])
|
|
}
|
|
>
|
|
{t('+ Feld')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|