frontend_nyla/src/components/Automation2FlowEditor/nodes/start/FormStartNodeConfig.tsx

122 lines
4.3 KiB
TypeScript

/**
* Start node (Formular) — define fields that appear at run time (payload.*).
*/
import React, { useMemo } from 'react';
import type { NodeConfigRendererProps } from '../configs/types';
import styles from '../../editor/Automation2FlowEditor.module.css';
type FormField = {
name: string;
label: string;
type: 'text' | 'number' | 'email' | 'date' | 'boolean' | 'clickup_status';
statusOptions?: Array<{ value: string; label: string }>;
};
const FORM_FIELD_TYPES = ['text', 'number', 'email', 'date', 'boolean', 'clickup_status'] as const;
function parseFields(params: Record<string, unknown>): FormField[] {
const raw = params.formFields;
if (!Array.isArray(raw)) return [{ name: 'field1', label: '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 t = String(o.type ?? 'text');
const name = String(o.name ?? `field${i + 1}`);
const label = String(o.label ?? `Feld ${i + 1}`);
const type = (
FORM_FIELD_TYPES.includes(t as (typeof FORM_FIELD_TYPES)[number]) ? t : 'text'
) as FormField['type'];
if (type === 'clickup_status' && Array.isArray(o.statusOptions)) {
return {
name,
label,
type: 'clickup_status',
statusOptions: o.statusOptions as Array<{ value: string; label: string }>,
};
}
return { name, label, type };
}
return { name: `field${i + 1}`, label: `Feld ${i + 1}`, type: 'text' as const };
});
}
export const FormStartNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, updateParam }) => {
const fields = useMemo(() => parseFields(params), [params]);
const setFields = (next: FormField[]) => {
updateParam('formFields', next);
};
return (
<div className={styles.startNodeDoc}>
<p className={styles.startNodeDocIntro}>
<strong>Formular-Felder</strong> werden beim Start ausgefüllt und liegen unter{' '}
<code>payload.&lt;name&gt;</code> in der Start-Ausgabe.
</p>
<div className={styles.formFieldsList}>
{fields.map((f, idx) => (
<div key={idx} className={styles.formFieldRow}>
<input
className={styles.startsInput}
placeholder="Name (Payload-Key)"
value={f.name}
onChange={(e) => {
const next = [...fields];
next[idx] = { ...f, name: e.target.value };
setFields(next);
}}
/>
<input
className={styles.startsInput}
placeholder="Beschriftung"
value={f.label}
onChange={(e) => {
const next = [...fields];
next[idx] = { ...f, label: e.target.value };
setFields(next);
}}
/>
<select
className={styles.startsSelect}
value={f.type}
onChange={(e) => {
const next = [...fields];
const t = e.target.value as FormField['type'];
if (t === 'clickup_status') {
next[idx] = { name: f.name, label: f.label, type: 'clickup_status', statusOptions: f.statusOptions };
} else {
next[idx] = { name: f.name, label: f.label, type: t };
}
setFields(next);
}}
>
<option value="text">Text</option>
<option value="number">Zahl</option>
<option value="email">E-Mail</option>
<option value="date">Datum</option>
<option value="boolean">Ja/Nein</option>
<option value="clickup_status">ClickUp-Status (Liste)</option>
</select>
<button
type="button"
className={styles.formFieldRemoveButton}
onClick={() => setFields(fields.filter((_, j) => j !== idx))}
>
</button>
</div>
))}
<button
type="button"
className={styles.startsAddBtn}
onClick={() =>
setFields([...fields, { name: `field${fields.length + 1}`, label: 'Neues Feld', type: 'text' }])
}
>
+ Feld
</button>
</div>
</div>
);
};