import React, { useState, useEffect } from 'react';
import HtmlEditorModal from './html.editor.modal.jsx';
function useDebouncedValue(value, delay = 300) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
export default function SidebarPanel({ node, onClose, onOpenVariables, onCursorCapture, exposeHandle }) {
if (!node) return null;
const [step, setStep] = useState('category'); // 'category' | 'list' | 'settings'
const [selectedCategoryId, setSelectedCategoryId] = useState(null);
const [selectedItem, setSelectedItem] = useState(null);
const [searchQuery, setSearchQuery] = useState('');
const debouncedSearch = useDebouncedValue(searchQuery, 300);
const [htmlEditorOpen, setHtmlEditorOpen] = useState(false);
const [htmlEditorKey, setHtmlEditorKey] = useState(null);
const [htmlEditorContent, setHtmlEditorContent] = useState('');
const nodeType = node?.type;
const config = window.WAIC_WORKFLOW?.blocks?.[nodeType+'s'] || {};
const lang = window.WAIC_WORKFLOW?.lang || {};
const categories = Object.entries(config).map(([key, value]) => ({
id: key || '',
name: value.name || '',
desc: value.desc || '',
items: value.list || [],
}));
const handleSettingChange = (key, value) => {
if (!node || !node.data || !selectedItem) return;
const currentValues = {
...node.data.settings,
[key]: value,
};
const errorKey = node.data.error || false;
const filteredSettings = getVisibleSettings(selectedItem.settings, currentValues);
onClose({
...node,
data: {
...node.data,
settings: filteredSettings,
error: errorKey === key ? false : errorKey,
},
});
};
useEffect(() => {
setSelectedItem(null);
setSelectedCategoryId(null);
setStep('category');
setSearchQuery('');
const { category, code } = node.data || {};
const safeCategory = category ?? '';
const categoryConfig = config[safeCategory] || config[''];
setSearchQuery('');
if (code && categoryConfig?.list) {
const found = categoryConfig.list.find((item) => item.code === code);
if (found) {
setSelectedCategoryId(safeCategory);
setSelectedItem(found);
setStep('settings');
return;
}
}
setStep('category');
setSelectedCategoryId(null);
setSelectedItem(null);
}, [node?.id]);
useEffect(() => {
if (exposeHandle) {
exposeHandle(handleSettingChange);
}
waicInitTooltips('.waic-sidebar-body');
}, [node]);
useEffect(() => {
if (step === 'category') setSelectedCategoryId(null);
}, [step]);
const handleCategorySelect = (catId) => {
setSelectedCategoryId(catId);
setStep('list');
onClose({
...node,
data: {
...node.data,
type: nodeType,
category: catId,
error: false,
},
});
};
const handleItemSelect = (item) => {
setSelectedItem(item);
setStep('settings');
onClose({
...node,
data: {
...node.data,
type: nodeType,
category: selectedCategoryId ?? 'un',
code: item.code,
label: item.name,
settings: getVisibleSettings(item.settings, {}),
error: false,
},
});
};
const getVisibleSettings = (settingsConfig, values) => {
const result = {};
const effectiveValues = getEffectiveSettingsValues(settingsConfig, values);
Object.entries(settingsConfig).forEach(([key, setting]) => {
if (setting.inner) return;
if (!shouldShowSetting(setting, effectiveValues)) return;
//result[key] = effectiveValues[key];
if (key in settingsConfig) {
result[key] = effectiveValues[key];
}
if (setting.add) {
setting.add.forEach((addKey) => {
const addSetting = settingsConfig[addKey];
if (addSetting && shouldShowSetting(addSetting, effectiveValues)) {
result[addKey] = effectiveValues[addKey];
}
});
}
});
return result;
};
const renderLabel = (setting, key) => {
if (setting.label !== '') {
return (
{setting.label}
{setting.tooltip && (

)}
{setting.html && (
{
setHtmlEditorKey(key);
setHtmlEditorContent(node.data?.settings?.[key] ?? '');
setHtmlEditorOpen(true);
}}>
HTML
)}
{setting.variables && (
onOpenVariables(key)}>{lang?.['variables'] || ''}
)}
{setting.copy && (
waicCopyText(node.data?.settings?.[key] ?? '')}>{lang?.['copy'] || ''}
)}
);
}
return null;
};
const renderSidebarDesc = (item) => {
const desc = item?.desc ?? '';
if (desc !== '') {
return (
{desc}
);
}
return null;
};
const renderSettingField = (key, setting) => {
const value = node.data?.settings?.[key];
switch (setting.type) {
case 'input':
return (
<>
{renderLabel(setting, key)}
handleSettingChange(key, e.target.value)}
onFocus={(e) => onCursorCapture(e, key)}
onClick={(e) => onCursorCapture(e, key)}
onKeyUp={(e) => onCursorCapture(e, key)}
/>
>
);
case 'select':
return (
<>
{renderLabel(setting, key)}
>
);
case 'multiple':
return (
<>
{renderLabel(setting, key)}
>
);
case 'number':
return (
<>
{renderLabel(setting, key)}
handleSettingChange(key, e.target.value)}
/>
>
);
case 'datetime':
return (
<>
{renderLabel(setting, key)}
handleSettingChange(key, e.target.value)}
/>
>
);
case 'date':
return (
<>
{renderLabel(setting, key)}
handleSettingChange(key, e.target.value)}
/>
>
);
case 'time':
return (
<>
{renderLabel(setting, key)}
handleSettingChange(key, e.target.value)}
/>
>
);
case 'textarea':
return (
<>
{renderLabel(setting, key)}
>
);
case 'readonly':
let v = setting.default;
const taskId = window.WAIC_WORKFLOW?.task_id || 0;
if (taskId > 0 && setting.text) {
v = setting.text.replace('{task_id}', taskId).replace('{node_id}', node.id);
if (v != value) {
handleSettingChange(key, v);
}
}
return (
<>
{renderLabel(setting, key)}
>
);
default:
return null;
}
};
const shouldShowSetting = (setting, allValues) => {
if (!setting.show) return true;
return Object.entries(setting.show).every(([depKey, allowedValues]) =>
allowedValues.includes(allValues?.[depKey])
);
};
const getEffectiveSettingsValues = (settingsConfig, nodeDataSettings) => {
const result = {};
Object.entries(settingsConfig).forEach(([key, setting]) => {
result[key] = nodeDataSettings?.[key] ?? setting.default ?? '';
});
return result;
};
const imgPath = window.WAIC_DATA.imgPath;
return (
<>

onClose(null)}/>
{step === 'category' && (
<>
{lang?.['сhoose']} {nodeType}
{categories.map((cat) =>
cat.id === 'un' ? (
cat.items.map((item) => (
- {
setSelectedCategoryId(cat.id);
handleItemSelect(item);
}}>
{item.name}
{item.desc}
))
) : (
- handleCategorySelect(cat.id)}>
{cat.name}
{cat.desc}
)
)}
>
)}
{step === 'list' && (
<>
setStep('category')}>
{config[selectedCategoryId]?.name ?? ''}
{renderSidebarDesc(config[selectedCategoryId])}
setSearchQuery(e.target.value)}
/>
{(config[selectedCategoryId]?.list || [])
.filter((item) =>
item.name?.toLowerCase().includes(debouncedSearch.toLowerCase()) ||
item.desc?.toLowerCase().includes(debouncedSearch.toLowerCase())
)
.map((item) => (
- handleItemSelect(item)}>
{item?.name ?? ''}
{item?.desc ?? ''}
))
}
>
)}
{step === 'settings' && selectedItem && selectedItem.code === node.data?.code && (
<>
{
if (!selectedCategoryId || selectedCategoryId === 'un' || selectedCategoryId === '') {
setStep('category');
} else {
setStep('list');
}}}>
{selectedItem?.name ?? ''}
{renderSidebarDesc(selectedItem)}
{Object.entries(selectedItem.settings || {}).map(([key, setting]) => {
if (setting.inner) return null;
//const currentValues = node.data?.settings || {};
const currentValues = getEffectiveSettingsValues(selectedItem.settings, node.data?.settings);
if (!shouldShowSetting(setting, currentValues)) return null;
const addFields = setting.add?.map((addKey) => {
const addSetting = selectedItem.settings?.[addKey];
if (!addSetting) return null;
if (!shouldShowSetting(addSetting, currentValues)) return null;
return (
<>
{renderSettingField(addKey, addSetting)}
>
);
});
return (
{renderSettingField(key, setting)}
{addFields}
);
})}
>
)}
>
{
setHtmlEditorOpen(false);
if (htmlEditorKey) {
handleSettingChange(htmlEditorKey, updatedContent);
}
}}
/>
);
}