frontend_nyla/src/components/FlowEditor/editor/NodeConfigPanel.tsx
2026-04-11 19:44:52 +02:00

122 lines
4.1 KiB
TypeScript

/**
* NodeConfigPanel - Generic parameter renderer for all node types.
* Renders each parameter using FRONTEND_TYPE_RENDERERS based on frontendType.
*/
import React, { useState, useEffect, useCallback, useRef } from 'react';
import type { CanvasNode } from './FlowCanvas';
import type { NodeType, NodeTypeParameter } from '../../../api/workflowApi';
import type { ApiRequestFunction } from '../../../api/workflowApi';
import { getLabel } from '../nodes/shared/utils';
import { FRONTEND_TYPE_RENDERERS } from '../nodes/frontendTypeRenderers';
import styles from './Automation2FlowEditor.module.css';
import { useLanguage } from '../../../providers/language/LanguageContext';
interface NodeConfigPanelProps {
node: CanvasNode | null;
nodeType: NodeType | undefined;
language: string;
onParametersChange: (nodeId: string, parameters: Record<string, unknown>) => void;
onMergeNodeParameters?: (nodeId: string, patch: Record<string, unknown>) => void;
onNodeUpdate?: (nodeId: string, updates: Partial<Pick<CanvasNode, 'title' | 'comment'>>) => void;
instanceId?: string;
request?: ApiRequestFunction;
}
export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
nodeType,
language,
onParametersChange,
onMergeNodeParameters: _onMergeNodeParameters,
onNodeUpdate,
instanceId,
request,
}) => {
const { t } = useLanguage();
const [params, setParams] = useState<Record<string, unknown>>({});
const nodeIdRef = useRef<string | undefined>(undefined);
nodeIdRef.current = node?.id;
const notifyParentTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
setParams(node?.parameters ?? {});
}, [node?.id, node?.parameters]);
useEffect(() => {
return () => {
if (notifyParentTimeoutRef.current != null) {
clearTimeout(notifyParentTimeoutRef.current);
notifyParentTimeoutRef.current = null;
}
};
}, [node?.id]);
const updateParam = useCallback(
(key: string, value: unknown) => {
setParams((prev) => {
const next = { ...prev, [key]: value };
const id = nodeIdRef.current;
if (id) {
if (notifyParentTimeoutRef.current != null) {
clearTimeout(notifyParentTimeoutRef.current);
}
notifyParentTimeoutRef.current = setTimeout(() => {
notifyParentTimeoutRef.current = null;
onParametersChange(id, next);
}, 0);
}
return next;
});
},
[onParametersChange]
);
if (!node || !nodeType) return null;
const isTrigger = node.type.startsWith('trigger.');
const showNameField = onNodeUpdate && !isTrigger;
const parameters = nodeType.parameters || [];
return (
<div className={styles.nodeConfigPanel}>
{showNameField && (
<div className={styles.nodeConfigNameRow}>
<label htmlFor="node-config-name">{t('Bezeichnung')}</label>
<input
id="node-config-name"
type="text"
value={node.title ?? ''}
onChange={(e) => onNodeUpdate(node.id, { title: e.target.value })}
placeholder={t('z.B. Kundenformular prüfen, Land')}
/>
<p className={styles.nodeConfigNameHint}>
{t('Wird im Data Picker angezeigt, um diesen Node zu identifizieren.')}
</p>
</div>
)}
<h4>{getLabel(nodeType?.label, language) || node.type}</h4>
{nodeType?.description && (
<p className={styles.nodeConfigDescription}>
{getLabel(nodeType.description, language)}
</p>
)}
{parameters.map((param: NodeTypeParameter) => {
const frontendType = param.frontendType || 'text';
const Renderer = FRONTEND_TYPE_RENDERERS[frontendType] ?? FRONTEND_TYPE_RENDERERS.text;
return (
<Renderer
key={param.name}
param={param}
value={params[param.name] ?? param.default}
onChange={(val: unknown) => updateParam(param.name, val)}
allParams={params}
instanceId={instanceId}
request={request}
nodeType={node.type}
/>
);
})}
</div>
);
};