fix: formular trigger
This commit is contained in:
parent
50a3df5c18
commit
dd26ea132d
2 changed files with 363 additions and 214 deletions
|
|
@ -0,0 +1,236 @@
|
|||
/**
|
||||
* Runtime form fields — shared by Human Task input.form and workflow list trigger.form start.
|
||||
* Field rows match task.config.fields / graph node parameters.formFields shape from the backend.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useApiRequest } from '../../../hooks/useApi';
|
||||
import { loadClickupListTasksForDropdown, type ApiRequestFunction } from '../../../api/workflowApi';
|
||||
import { normalizeFormFieldOptions } from '../nodes/form';
|
||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||
|
||||
export type WorkflowRuntimeFormFieldRow = {
|
||||
name: string;
|
||||
type: string;
|
||||
label: string;
|
||||
required?: boolean;
|
||||
options?: unknown;
|
||||
clickupConnectionId?: string;
|
||||
clickupListId?: string;
|
||||
clickupStatusOptions?: Array<{ value: string; label: string }>;
|
||||
};
|
||||
|
||||
export function relationshipTaskIdFromFormValue(v: unknown): string {
|
||||
if (v && typeof v === 'object' && !Array.isArray(v) && 'add' in v) {
|
||||
const a = (v as { add?: unknown[] }).add;
|
||||
if (Array.isArray(a) && a[0] != null && String(a[0]).trim()) return String(a[0]);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function InputFormClickupTaskField({
|
||||
connectionId,
|
||||
listId,
|
||||
value,
|
||||
onChange,
|
||||
request,
|
||||
}: {
|
||||
connectionId: string;
|
||||
listId: string;
|
||||
value: unknown;
|
||||
onChange: (v: unknown) => void;
|
||||
request: ApiRequestFunction;
|
||||
}) {
|
||||
const { t } = useLanguage();
|
||||
const [tasks, setTasks] = useState<Array<{ id: string; name: string }>>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [err, setErr] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const cid = connectionId.trim();
|
||||
const lid = listId.trim();
|
||||
if (!cid || !lid) {
|
||||
setTasks([]);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
setLoading(true);
|
||||
setErr(null);
|
||||
loadClickupListTasksForDropdown(request, cid, lid)
|
||||
.then((rows) => {
|
||||
if (!cancelled) setTasks(rows);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) {
|
||||
setTasks([]);
|
||||
setErr(t('Aufgaben konnten nicht geladen werden.'));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setLoading(false);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [request, connectionId, listId, t]);
|
||||
|
||||
const sel = relationshipTaskIdFromFormValue(value);
|
||||
|
||||
if (!connectionId.trim() || !listId.trim()) {
|
||||
return (
|
||||
<p style={{ fontSize: '0.85rem', color: 'var(--text-secondary, #666)' }}>
|
||||
{t('Für dieses Feld sind im Formular-Node ClickUp-Verbindung und Listen-ID gesetzt — bitte Workflow prüfen.')}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{err ? (
|
||||
<p style={{ fontSize: '0.85rem', color: 'var(--text-secondary, #c00)' }}>{err}</p>
|
||||
) : null}
|
||||
{loading ? (
|
||||
<p style={{ fontSize: '0.85rem', color: 'var(--text-secondary, #666)' }}>{t('lade Aufgaben')}</p>
|
||||
) : (
|
||||
<select
|
||||
value={sel}
|
||||
onChange={(e) => {
|
||||
const tid = e.target.value;
|
||||
if (!tid) onChange({ add: [], rem: [] });
|
||||
else onChange({ add: [tid], rem: [] });
|
||||
}}
|
||||
>
|
||||
<option value="">{t('Aufgabe wählen')}</option>
|
||||
{tasks.map((taskRow) => (
|
||||
<option key={taskRow.id} value={taskRow.id}>
|
||||
{taskRow.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function useWorkflowRuntimeFormRequiredOk(
|
||||
fields: WorkflowRuntimeFormFieldRow[],
|
||||
formData: Record<string, unknown>
|
||||
): boolean {
|
||||
const requiredFields = fields.filter((f) => f.required);
|
||||
return requiredFields.every((f) => {
|
||||
const v = formData[f.name];
|
||||
if (f.type === 'boolean') return true;
|
||||
if (f.type === 'clickup_tasks') {
|
||||
return relationshipTaskIdFromFormValue(v) !== '';
|
||||
}
|
||||
if (f.type === 'clickup_status') {
|
||||
return v !== undefined && v !== null && String(v).trim() !== '';
|
||||
}
|
||||
if (
|
||||
(f.type === 'select' || f.type === 'enum') &&
|
||||
normalizeFormFieldOptions(f.options).some((o) => String(o.value).trim() !== '')
|
||||
) {
|
||||
return v !== undefined && v !== null && String(v).trim() !== '';
|
||||
}
|
||||
return v !== undefined && v !== null && String(v).trim() !== '';
|
||||
});
|
||||
}
|
||||
|
||||
export interface WorkflowRuntimeFormFieldsProps {
|
||||
fields: WorkflowRuntimeFormFieldRow[];
|
||||
formData: Record<string, unknown>;
|
||||
setFormData: React.Dispatch<React.SetStateAction<Record<string, unknown>>>;
|
||||
formFieldsClassName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the same controls as TaskCard input.form (no Popup — parent wraps if needed).
|
||||
*/
|
||||
export const WorkflowRuntimeFormFields: React.FC<WorkflowRuntimeFormFieldsProps> = ({
|
||||
fields,
|
||||
formData,
|
||||
setFormData,
|
||||
formFieldsClassName,
|
||||
}) => {
|
||||
const { t } = useLanguage();
|
||||
const { request } = useApiRequest();
|
||||
|
||||
const renderFormControl = (field: WorkflowRuntimeFormFieldRow): React.ReactNode => {
|
||||
const selectChoices = normalizeFormFieldOptions(field.options).filter((o) => String(o.value).trim() !== '');
|
||||
if (field.type === 'boolean') {
|
||||
return (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={(formData[field.name] as boolean) ?? false}
|
||||
onChange={(e) => setFormData((p) => ({ ...p, [field.name]: e.target.checked }))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (field.type === 'clickup_tasks' && request) {
|
||||
return (
|
||||
<InputFormClickupTaskField
|
||||
connectionId={field.clickupConnectionId ?? ''}
|
||||
listId={field.clickupListId ?? ''}
|
||||
value={formData[field.name]}
|
||||
onChange={(v) => setFormData((p) => ({ ...p, [field.name]: v }))}
|
||||
request={request}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (
|
||||
field.type === 'clickup_status' &&
|
||||
Array.isArray(field.clickupStatusOptions) &&
|
||||
field.clickupStatusOptions.length > 0
|
||||
) {
|
||||
return (
|
||||
<select
|
||||
value={(formData[field.name] as string) ?? ''}
|
||||
onChange={(e) => setFormData((p) => ({ ...p, [field.name]: e.target.value }))}
|
||||
>
|
||||
<option value="">{t('Status wählen')}</option>
|
||||
{field.clickupStatusOptions.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
if ((field.type === 'select' || field.type === 'enum') && selectChoices.length > 0) {
|
||||
return (
|
||||
<select
|
||||
value={(formData[field.name] as string) ?? ''}
|
||||
onChange={(e) => setFormData((p) => ({ ...p, [field.name]: e.target.value }))}
|
||||
>
|
||||
<option value="">{t('Bitte wählen')}</option>
|
||||
{selectChoices.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label || o.value}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<input
|
||||
type={field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'}
|
||||
value={(formData[field.name] as string) ?? ''}
|
||||
onChange={(e) => setFormData((p) => ({ ...p, [field.name]: e.target.value }))}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={formFieldsClassName}>
|
||||
{fields.map((f) => (
|
||||
<div key={f.name}>
|
||||
<label>
|
||||
{f.label || f.name}
|
||||
{f.required && ' *'}
|
||||
</label>
|
||||
{renderFormControl(f)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -17,18 +17,20 @@ import {
|
|||
fetchCompletedRuns,
|
||||
fetchWorkflows,
|
||||
executeGraph,
|
||||
loadClickupListTasksForDropdown,
|
||||
type Automation2Task,
|
||||
type Automation2Workflow,
|
||||
type CompletedRun,
|
||||
type ApiRequestFunction,
|
||||
} from '../../../api/workflowApi';
|
||||
import { useToast } from '../../../contexts/ToastContext';
|
||||
import { Popup } from '../../../components/UiComponents/Popup';
|
||||
import { getAcceptStringFromConfig, fileMatchesAccept } from '../../../components/FlowEditor';
|
||||
import { useFileOperations } from '../../../hooks/useFiles';
|
||||
import styles from './Automation2WorkflowsTasks.module.css';
|
||||
import { normalizeFormFieldOptions } from '../../../components/FlowEditor/nodes/form';
|
||||
import {
|
||||
WorkflowRuntimeFormFields,
|
||||
useWorkflowRuntimeFormRequiredOk,
|
||||
type WorkflowRuntimeFormFieldRow,
|
||||
} from '../../../components/FlowEditor/workflowRuntime/WorkflowRuntimeFormFields';
|
||||
|
||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||
|
||||
|
|
@ -77,17 +79,38 @@ function hasManualOrFormInvocation(wf: Automation2Workflow): boolean {
|
|||
}
|
||||
|
||||
/**
|
||||
* Primary entry for execute — POST /api/workflows/{instanceId}/execute with collected inputs.
|
||||
* (manual first, then form or api).
|
||||
* Primary entry for execute — align with first start node in graph order (backend-driven),
|
||||
* then fall back to manual / form / api on invocations list.
|
||||
*/
|
||||
function getPrimaryEntryPoint(wf: Automation2Workflow) {
|
||||
const invs = wf.invocations || [];
|
||||
const nodes = wf.graph?.nodes ?? [];
|
||||
for (const n of nodes) {
|
||||
const nodeType = n.type;
|
||||
if (typeof nodeType === 'string' && nodeType.startsWith('trigger.')) {
|
||||
const inv = invs.find((i) => i.enabled !== false && i.id === n.id);
|
||||
if (inv) return inv;
|
||||
}
|
||||
}
|
||||
return (
|
||||
invs.find((i) => i.enabled !== false && i.kind === 'manual') ||
|
||||
invs.find((i) => i.enabled !== false && (i.kind === 'form' || i.kind === 'api'))
|
||||
);
|
||||
}
|
||||
|
||||
/** Form field rows from graph trigger.form for workflow list (parameters.formFields). */
|
||||
function getTriggerFormFieldsForWorkflow(wf: Automation2Workflow): WorkflowRuntimeFormFieldRow[] {
|
||||
const primary = getPrimaryEntryPoint(wf);
|
||||
if (!primary || primary.kind !== 'form') return [];
|
||||
const nodes = wf.graph?.nodes ?? [];
|
||||
let node = nodes.find((n) => n.id === primary.id && n.type === 'trigger.form');
|
||||
if (!node) node = nodes.find((n) => n.type === 'trigger.form');
|
||||
if (!node) return [];
|
||||
const raw = (node.parameters as Record<string, unknown> | undefined)?.formFields;
|
||||
if (!Array.isArray(raw)) return [];
|
||||
return raw as WorkflowRuntimeFormFieldRow[];
|
||||
}
|
||||
|
||||
function primaryKindLabel(kind: string): string {
|
||||
if (kind === 'form') return 'Formular';
|
||||
if (kind === 'manual') return 'Manuell';
|
||||
|
|
@ -109,6 +132,9 @@ export const GraphicalEditorWorkflowsTasksPage: React.FC = () => {
|
|||
const [submitting, setSubmitting] = useState<string | null>(null);
|
||||
const [dismissingTaskId, setDismissingTaskId] = useState<string | null>(null);
|
||||
const [executingWorkflowId, setExecutingWorkflowId] = useState<string | null>(null);
|
||||
const [formStartWorkflow, setFormStartWorkflow] = useState<Automation2Workflow | null>(null);
|
||||
const [formStartFields, setFormStartFields] = useState<WorkflowRuntimeFormFieldRow[]>([]);
|
||||
const [startFormData, setStartFormData] = useState<Record<string, unknown>>({});
|
||||
|
||||
const load = useCallback(async () => {
|
||||
if (!instanceId) return;
|
||||
|
|
@ -185,6 +211,12 @@ export const GraphicalEditorWorkflowsTasksPage: React.FC = () => {
|
|||
async (wf: Automation2Workflow) => {
|
||||
if (!instanceId || !wf.graph) return;
|
||||
const primary = getPrimaryEntryPoint(wf);
|
||||
if (primary?.kind === 'form') {
|
||||
setFormStartFields(getTriggerFormFieldsForWorkflow(wf));
|
||||
setStartFormData({});
|
||||
setFormStartWorkflow(wf);
|
||||
return;
|
||||
}
|
||||
setExecutingWorkflowId(wf.id);
|
||||
try {
|
||||
const result = await executeGraph(request, instanceId, wf.graph, wf.id, {
|
||||
|
|
@ -211,6 +243,48 @@ export const GraphicalEditorWorkflowsTasksPage: React.FC = () => {
|
|||
[instanceId, request, showSuccess, showError, load, t]
|
||||
);
|
||||
|
||||
const formStartRequiredOk = useWorkflowRuntimeFormRequiredOk(formStartFields, startFormData);
|
||||
|
||||
const handleFormStartSubmit = useCallback(async () => {
|
||||
if (!instanceId || !formStartWorkflow?.graph) return;
|
||||
const wf = formStartWorkflow;
|
||||
const primary = getPrimaryEntryPoint(wf);
|
||||
const payload = { ...startFormData };
|
||||
setExecutingWorkflowId(wf.id);
|
||||
try {
|
||||
const result = await executeGraph(request, instanceId, wf.graph, wf.id, {
|
||||
...(primary ? { entryPointId: primary.id } : {}),
|
||||
payload,
|
||||
});
|
||||
if (result?.success) {
|
||||
if (result?.paused) {
|
||||
showSuccess(t('Workflow gestartet und bei Human Task pausiert.'));
|
||||
} else {
|
||||
showSuccess(t('Workflow gestartet'));
|
||||
}
|
||||
await load();
|
||||
} else {
|
||||
showError(result?.error || t('Ausführung fehlgeschlagen'));
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
const msg =
|
||||
(e as { message?: string })?.message ?? t('Ausführung fehlgeschlagen');
|
||||
showError(msg);
|
||||
} finally {
|
||||
setExecutingWorkflowId(null);
|
||||
setFormStartWorkflow(null);
|
||||
}
|
||||
}, [
|
||||
instanceId,
|
||||
formStartWorkflow,
|
||||
startFormData,
|
||||
request,
|
||||
showSuccess,
|
||||
showError,
|
||||
load,
|
||||
t,
|
||||
]);
|
||||
|
||||
const openTasks = tasks.filter((task) => task.status === 'pending');
|
||||
const completedTasks = tasks.filter((task) => task.status !== 'pending');
|
||||
|
||||
|
|
@ -364,6 +438,41 @@ export const GraphicalEditorWorkflowsTasksPage: React.FC = () => {
|
|||
)}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<Popup
|
||||
isOpen={formStartWorkflow != null}
|
||||
title={t('Formular ausfüllen')}
|
||||
onClose={() => setFormStartWorkflow(null)}
|
||||
closable={
|
||||
!(formStartWorkflow != null && executingWorkflowId === formStartWorkflow.id)
|
||||
}
|
||||
closeOnEscape={
|
||||
!(formStartWorkflow != null && executingWorkflowId === formStartWorkflow.id)
|
||||
}
|
||||
size="medium"
|
||||
footerContent={
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void handleFormStartSubmit()}
|
||||
disabled={
|
||||
!formStartRequiredOk ||
|
||||
(formStartWorkflow != null && executingWorkflowId === formStartWorkflow.id)
|
||||
}
|
||||
className={styles.popupSubmitButton}
|
||||
>
|
||||
{formStartWorkflow != null && executingWorkflowId === formStartWorkflow.id
|
||||
? t('wird gesendet')
|
||||
: t('absenden')}
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<WorkflowRuntimeFormFields
|
||||
fields={formStartFields}
|
||||
formData={startFormData}
|
||||
setFormData={setStartFormData}
|
||||
formFieldsClassName={styles.formFields}
|
||||
/>
|
||||
</Popup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -439,99 +548,6 @@ interface TaskCardProps {
|
|||
dismissing?: boolean;
|
||||
}
|
||||
|
||||
/** Check if file matches accept string (e.g. ".pdf,image/*"). */
|
||||
function relationshipTaskIdFromFormValue(v: unknown): string {
|
||||
if (v && typeof v === 'object' && !Array.isArray(v) && 'add' in v) {
|
||||
const a = (v as { add?: unknown[] }).add;
|
||||
if (Array.isArray(a) && a[0] != null && String(a[0]).trim()) return String(a[0]);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function InputFormClickupTaskField({
|
||||
connectionId,
|
||||
listId,
|
||||
value,
|
||||
onChange,
|
||||
request,
|
||||
}: {
|
||||
connectionId: string;
|
||||
listId: string;
|
||||
value: unknown;
|
||||
onChange: (v: unknown) => void;
|
||||
request: ApiRequestFunction;
|
||||
}) {
|
||||
const { t } = useLanguage();
|
||||
const [tasks, setTasks] = useState<Array<{ id: string; name: string }>>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [err, setErr] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const cid = connectionId.trim();
|
||||
const lid = listId.trim();
|
||||
if (!cid || !lid) {
|
||||
setTasks([]);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
setLoading(true);
|
||||
setErr(null);
|
||||
loadClickupListTasksForDropdown(request, cid, lid)
|
||||
.then((rows) => {
|
||||
if (!cancelled) setTasks(rows);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) {
|
||||
setTasks([]);
|
||||
setErr(t('Aufgaben konnten nicht geladen werden.'));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setLoading(false);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [request, connectionId, listId]);
|
||||
|
||||
const sel = relationshipTaskIdFromFormValue(value);
|
||||
|
||||
if (!connectionId.trim() || !listId.trim()) {
|
||||
return (
|
||||
<p style={{ fontSize: '0.85rem', color: 'var(--text-secondary, #666)' }}>
|
||||
{t('Für dieses Feld sind im Formular-Node ClickUp-Verbindung und Listen-ID gesetzt — bitte Workflow prüfen.')}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{err ? (
|
||||
<p style={{ fontSize: '0.85rem', color: 'var(--text-secondary, #c00)' }}>{err}</p>
|
||||
) : null}
|
||||
{loading ? (
|
||||
<p style={{ fontSize: '0.85rem', color: 'var(--text-secondary, #666)' }}>{t('lade Aufgaben')}</p>
|
||||
) : (
|
||||
<select
|
||||
value={sel}
|
||||
onChange={(e) => {
|
||||
const tid = e.target.value;
|
||||
if (!tid) onChange({ add: [], rem: [] });
|
||||
else onChange({ add: [tid], rem: [] });
|
||||
}}
|
||||
>
|
||||
<option value="">{t('Aufgabe wählen')}</option>
|
||||
{tasks.map((taskRow) => (
|
||||
<option key={taskRow.id} value={taskRow.id}>
|
||||
{taskRow.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const TaskCard: React.FC<TaskCardProps> = ({
|
||||
task,
|
||||
instanceId,
|
||||
|
|
@ -555,6 +571,12 @@ const TaskCard: React.FC<TaskCardProps> = ({
|
|||
const nodeType = task.nodeType;
|
||||
const stepLabel = getNodeStepLabel(config);
|
||||
|
||||
const inputFormFields: WorkflowRuntimeFormFieldRow[] =
|
||||
nodeType === 'input.form'
|
||||
? ((config.fields as WorkflowRuntimeFormFieldRow[]) ?? [])
|
||||
: [];
|
||||
const inputFormRequiredOk = useWorkflowRuntimeFormRequiredOk(inputFormFields, formData);
|
||||
|
||||
useEffect(() => {
|
||||
setUploadedFiles([]);
|
||||
setUploadError(null);
|
||||
|
|
@ -564,122 +586,13 @@ const TaskCard: React.FC<TaskCardProps> = ({
|
|||
if (readOnly) return null;
|
||||
switch (nodeType) {
|
||||
case 'input.form': {
|
||||
const fields =
|
||||
(config.fields as Array<{
|
||||
name: string;
|
||||
type: string;
|
||||
label: string;
|
||||
required?: boolean;
|
||||
options?: unknown;
|
||||
clickupConnectionId?: string;
|
||||
clickupListId?: string;
|
||||
clickupStatusOptions?: Array<{ value: string; label: string }>;
|
||||
}>) ?? [];
|
||||
const requiredFields = fields.filter((f) => f.required);
|
||||
const allRequiredFilled = requiredFields.every((f) => {
|
||||
const v = formData[f.name];
|
||||
if (f.type === 'boolean') return true;
|
||||
if (f.type === 'clickup_tasks') {
|
||||
return relationshipTaskIdFromFormValue(v) !== '';
|
||||
}
|
||||
if (f.type === 'clickup_status') {
|
||||
return v !== undefined && v !== null && String(v).trim() !== '';
|
||||
}
|
||||
if ((f.type === 'select' || f.type === 'enum') && normalizeFormFieldOptions(f.options).some((o) => String(o.value).trim() !== '')) {
|
||||
return v !== undefined && v !== null && String(v).trim() !== '';
|
||||
}
|
||||
return v !== undefined && v !== null && String(v).trim() !== '';
|
||||
});
|
||||
const renderFormControl = (
|
||||
field: (typeof fields)[number],
|
||||
): React.ReactNode => {
|
||||
const selectChoices = normalizeFormFieldOptions(field.options).filter(
|
||||
(o) => String(o.value).trim() !== '',
|
||||
);
|
||||
if (field.type === 'boolean') {
|
||||
return (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={(formData[field.name] as boolean) ?? false}
|
||||
onChange={(e) =>
|
||||
setFormData((p) => ({ ...p, [field.name]: e.target.checked }))
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (field.type === 'clickup_tasks' && request) {
|
||||
return (
|
||||
<InputFormClickupTaskField
|
||||
connectionId={field.clickupConnectionId ?? ''}
|
||||
listId={field.clickupListId ?? ''}
|
||||
value={formData[field.name]}
|
||||
onChange={(v) => setFormData((p) => ({ ...p, [field.name]: v }))}
|
||||
request={request}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (
|
||||
field.type === 'clickup_status' &&
|
||||
Array.isArray(field.clickupStatusOptions) &&
|
||||
field.clickupStatusOptions.length > 0
|
||||
) {
|
||||
return (
|
||||
<select
|
||||
value={(formData[field.name] as string) ?? ''}
|
||||
onChange={(e) =>
|
||||
setFormData((p) => ({ ...p, [field.name]: e.target.value }))
|
||||
}
|
||||
>
|
||||
<option value="">{t('Status wählen')}</option>
|
||||
{field.clickupStatusOptions.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
if ((field.type === 'select' || field.type === 'enum') && selectChoices.length > 0) {
|
||||
return (
|
||||
<select
|
||||
value={(formData[field.name] as string) ?? ''}
|
||||
onChange={(e) =>
|
||||
setFormData((p) => ({ ...p, [field.name]: e.target.value }))
|
||||
}
|
||||
>
|
||||
<option value="">{t('Bitte wählen')}</option>
|
||||
{selectChoices.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label || o.value}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<input
|
||||
type={
|
||||
field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'
|
||||
}
|
||||
value={(formData[field.name] as string) ?? ''}
|
||||
onChange={(e) =>
|
||||
setFormData((p) => ({ ...p, [field.name]: e.target.value }))
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const formContent = (
|
||||
<div className={styles.formFields}>
|
||||
{fields.map((f) => (
|
||||
<div key={f.name}>
|
||||
<label>
|
||||
{f.label || f.name}
|
||||
{f.required && ' *'}
|
||||
</label>
|
||||
{renderFormControl(f)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<WorkflowRuntimeFormFields
|
||||
fields={inputFormFields}
|
||||
formData={formData}
|
||||
setFormData={setFormData}
|
||||
formFieldsClassName={styles.formFields}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
|
|
@ -704,7 +617,7 @@ const TaskCard: React.FC<TaskCardProps> = ({
|
|||
onSubmit({ payload: formData });
|
||||
setFormPopupOpen(false);
|
||||
}}
|
||||
disabled={submitting || !allRequiredFilled}
|
||||
disabled={submitting || !inputFormRequiredOk}
|
||||
className={styles.popupSubmitButton}
|
||||
>
|
||||
{submitting ? t('wird gesendet') : t('absenden')}
|
||||
|
|
|
|||
Loading…
Reference in a new issue