122 lines
4.3 KiB
TypeScript
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.<name></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>
|
|
);
|
|
};
|