FEAT: Incrementa gestao do fluxo do bot
This commit is contained in:
parent
dcad70b708
commit
751038be0f
@ -78,6 +78,32 @@ function collectPublishWarnings(node, warnings = []) {
|
||||
return warnings;
|
||||
}
|
||||
|
||||
function getFlowNodeWidth(level) {
|
||||
return level >= 2 ? 260 : 300;
|
||||
}
|
||||
|
||||
function getFlowChildGap(level) {
|
||||
return level >= 2 ? 56 : 40;
|
||||
}
|
||||
|
||||
function getFlowSubtreeWidth(node, level = 0) {
|
||||
if (!node) return getFlowNodeWidth(level);
|
||||
const children = node.children || [];
|
||||
const nodeWidth = getFlowNodeWidth(level);
|
||||
const horizontalPadding = level >= 2 ? 56 : 72;
|
||||
|
||||
if (!children.length) {
|
||||
return nodeWidth + horizontalPadding;
|
||||
}
|
||||
|
||||
const gap = getFlowChildGap(level);
|
||||
const childrenWidth =
|
||||
children.reduce((total, child) => total + getFlowSubtreeWidth(child, level + 1), 0) +
|
||||
Math.max(0, children.length - 1) * gap;
|
||||
|
||||
return Math.max(nodeWidth + horizontalPadding, childrenWidth);
|
||||
}
|
||||
|
||||
function WhatsAppPreview({ message }) {
|
||||
return (
|
||||
<div
|
||||
@ -108,18 +134,44 @@ function WhatsAppPreview({ message }) {
|
||||
);
|
||||
}
|
||||
|
||||
function FlowNode({ node, areasById, onAdd, onEdit, onDelete }) {
|
||||
function FlowNode({ node, areasById, onAdd, onEdit, onDelete, level = 0, parentTitle = '' }) {
|
||||
const keywords = splitKeywords(node.keywords);
|
||||
const isRoot = node.node_type === 'greeting';
|
||||
const isAgent = node.node_type === 'agent';
|
||||
const isClose = node.node_type === 'close';
|
||||
const isDeep = level >= 2;
|
||||
const nodeWidth = getFlowNodeWidth(level);
|
||||
const visibleKeywordLimit = isDeep ? 4 : 8;
|
||||
const childGap = getFlowChildGap(level);
|
||||
const subtreeWidth = getFlowSubtreeWidth(node, level);
|
||||
const firstChildWidth = node.children?.length ? getFlowSubtreeWidth(node.children[0], level + 1) : 0;
|
||||
const lastChildWidth = node.children?.length
|
||||
? getFlowSubtreeWidth(node.children[node.children.length - 1], level + 1)
|
||||
: 0;
|
||||
const accentColor = isRoot
|
||||
? 'var(--color-primary)'
|
||||
: isAgent
|
||||
? '#3260b3'
|
||||
: isClose
|
||||
? '#0f8f77'
|
||||
: 'var(--color-highlight)';
|
||||
const nodeMessage = node.message_text || (isAgent ? '' : 'Sem mensagem configurada.');
|
||||
|
||||
return (
|
||||
<div style={{ display: 'grid', justifyItems: 'center', gap: '0.8rem', minWidth: 260 }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
justifyItems: 'center',
|
||||
gap: '0.95rem',
|
||||
minWidth: subtreeWidth,
|
||||
width: subtreeWidth,
|
||||
}}
|
||||
>
|
||||
<article
|
||||
style={{
|
||||
width: 280,
|
||||
width: nodeWidth,
|
||||
border: '1px solid var(--color-border)',
|
||||
borderTop: `5px solid ${accentColor}`,
|
||||
borderRadius: 18,
|
||||
background: isRoot
|
||||
? 'linear-gradient(180deg, #fff, rgba(0,164,183,0.09))'
|
||||
@ -129,17 +181,36 @@ function FlowNode({ node, areasById, onAdd, onEdit, onDelete }) {
|
||||
? 'linear-gradient(180deg, #fff, rgba(0,164,183,0.1))'
|
||||
: '#fff',
|
||||
boxShadow: '0 12px 28px rgba(0, 49, 80, 0.08)',
|
||||
padding: '0.95rem',
|
||||
padding: isDeep ? '0.8rem' : '0.95rem',
|
||||
display: 'grid',
|
||||
gap: '0.7rem',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '0.7rem', alignItems: 'start' }}>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<span style={{ color: 'var(--color-primary)', fontSize: '0.74rem', fontWeight: 900, textTransform: 'uppercase' }}>
|
||||
{nodeTypeLabel(node.node_type)}
|
||||
</span>
|
||||
<div style={{ display: 'flex', gap: '0.4rem', alignItems: 'center', flexWrap: 'wrap', marginBottom: '0.15rem' }}>
|
||||
<span style={{ color: 'var(--color-primary)', fontSize: '0.74rem', fontWeight: 900, textTransform: 'uppercase' }}>
|
||||
{nodeTypeLabel(node.node_type)}
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
borderRadius: 999,
|
||||
padding: '0.12rem 0.42rem',
|
||||
background: 'rgba(0,49,80,0.06)',
|
||||
color: 'var(--color-text-soft)',
|
||||
fontSize: '0.68rem',
|
||||
fontWeight: 900,
|
||||
}}
|
||||
>
|
||||
Nível {level + 1}
|
||||
</span>
|
||||
</div>
|
||||
<strong style={{ display: 'block', lineHeight: 1.25 }}>{node.title}</strong>
|
||||
{!isRoot && parentTitle ? (
|
||||
<span style={{ display: 'block', marginTop: '0.22rem', color: 'var(--color-text-soft)', fontSize: '0.78rem', fontWeight: 700 }}>
|
||||
abaixo de: {parentTitle}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
{!isAgent && !isClose && onAdd ? (
|
||||
<button
|
||||
@ -167,30 +238,49 @@ function FlowNode({ node, areasById, onAdd, onEdit, onDelete }) {
|
||||
</span>
|
||||
) : isClose ? (
|
||||
<span style={{ color: 'var(--color-text-soft)', lineHeight: 1.35, whiteSpace: 'pre-wrap' }}>
|
||||
{node.message_text || 'Fecha o atendimento sem enviar para agente.'}
|
||||
{isDeep && nodeMessage.length > 96 ? `${nodeMessage.slice(0, 96)}...` : nodeMessage}
|
||||
</span>
|
||||
) : (
|
||||
<span style={{ color: 'var(--color-text-soft)', whiteSpace: 'pre-wrap', lineHeight: 1.35 }}>
|
||||
{node.message_text || 'Sem mensagem configurada.'}
|
||||
<span style={{ color: 'var(--color-text-soft)', whiteSpace: isDeep ? 'normal' : 'pre-wrap', lineHeight: 1.35 }}>
|
||||
{isDeep && nodeMessage.length > 96 ? `${nodeMessage.slice(0, 96)}...` : nodeMessage}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{!isRoot && keywords.length ? (
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.35rem' }}>
|
||||
{keywords.slice(0, 8).map((keyword) => (
|
||||
<span
|
||||
key={keyword}
|
||||
style={{
|
||||
borderRadius: 999,
|
||||
background: 'rgba(0,49,80,0.07)',
|
||||
padding: '0.22rem 0.5rem',
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 800,
|
||||
}}
|
||||
>
|
||||
{keyword}
|
||||
</span>
|
||||
))}
|
||||
<div style={{ display: 'grid', gap: '0.35rem' }}>
|
||||
<span style={{ color: 'var(--color-text-soft)', fontSize: '0.72rem', fontWeight: 900 }}>
|
||||
Respostas que chegam aqui
|
||||
</span>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.35rem' }}>
|
||||
{keywords.slice(0, visibleKeywordLimit).map((keyword) => (
|
||||
<span
|
||||
key={keyword}
|
||||
style={{
|
||||
borderRadius: 999,
|
||||
background: 'rgba(0,49,80,0.07)',
|
||||
padding: '0.22rem 0.5rem',
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 800,
|
||||
}}
|
||||
>
|
||||
{keyword}
|
||||
</span>
|
||||
))}
|
||||
{keywords.length > visibleKeywordLimit ? (
|
||||
<span
|
||||
style={{
|
||||
borderRadius: 999,
|
||||
background: 'rgba(0,49,80,0.04)',
|
||||
padding: '0.22rem 0.5rem',
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 800,
|
||||
color: 'var(--color-text-soft)',
|
||||
}}
|
||||
>
|
||||
+{keywords.length - visibleKeywordLimit}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@ -218,27 +308,76 @@ function FlowNode({ node, areasById, onAdd, onEdit, onDelete }) {
|
||||
|
||||
{node.children?.length ? (
|
||||
<>
|
||||
<div style={{ width: 2, height: 20, background: 'rgba(0,49,80,0.18)' }} />
|
||||
<div
|
||||
style={{
|
||||
width: 2,
|
||||
height: 38,
|
||||
background: 'linear-gradient(180deg, rgba(0,49,80,0.28), rgba(0,49,80,0.1))',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: '1rem',
|
||||
gap: childGap,
|
||||
alignItems: 'start',
|
||||
justifyContent: 'center',
|
||||
flexWrap: 'nowrap',
|
||||
paddingTop: '0.2rem',
|
||||
paddingTop: 34,
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{node.children.map((child) => (
|
||||
<FlowNode
|
||||
key={child.id}
|
||||
node={child}
|
||||
areasById={areasById}
|
||||
onAdd={onAdd}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
{node.children.length > 1 ? (
|
||||
<div
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: firstChildWidth / 2,
|
||||
right: lastChildWidth / 2,
|
||||
height: 2,
|
||||
background: 'rgba(0,49,80,0.16)',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
) : null}
|
||||
{node.children.map((child) => {
|
||||
const childWidth = getFlowSubtreeWidth(child, level + 1);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={child.id}
|
||||
style={{
|
||||
position: 'relative',
|
||||
display: 'grid',
|
||||
justifyItems: 'center',
|
||||
minWidth: childWidth,
|
||||
width: childWidth,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: -34,
|
||||
left: '50%',
|
||||
width: 2,
|
||||
height: 34,
|
||||
background: 'rgba(0,49,80,0.2)',
|
||||
transform: 'translateX(-50%)',
|
||||
}}
|
||||
/>
|
||||
<FlowNode
|
||||
node={child}
|
||||
areasById={areasById}
|
||||
onAdd={onAdd}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
level={level + 1}
|
||||
parentTitle={node.title}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
@ -550,6 +689,7 @@ export function KnowledgeBasePanel({ areas, mode = 'admin', isMobile = false })
|
||||
const root = flow?.tree;
|
||||
const hasPublished = Boolean(flow?.latestPublished);
|
||||
const publishWarnings = useMemo(() => collectPublishWarnings(root).slice(0, 5), [root]);
|
||||
const treeMinWidth = useMemo(() => Math.max(1100, getFlowSubtreeWidth(root)), [root]);
|
||||
|
||||
return (
|
||||
<div style={{ display: 'grid', gap: '1rem' }}>
|
||||
@ -624,7 +764,9 @@ export function KnowledgeBasePanel({ areas, mode = 'admin', isMobile = false })
|
||||
style={{
|
||||
border: '1px solid var(--color-border)',
|
||||
borderRadius: 22,
|
||||
background: 'linear-gradient(180deg, #fff, rgba(0,49,80,0.03))',
|
||||
background:
|
||||
'linear-gradient(180deg, #fff, rgba(0,49,80,0.03)), radial-gradient(circle at 1px 1px, rgba(0,49,80,0.08) 1px, transparent 0)',
|
||||
backgroundSize: 'auto, 22px 22px',
|
||||
overflow: 'auto',
|
||||
minHeight: 520,
|
||||
padding: '1.25rem',
|
||||
@ -634,7 +776,7 @@ export function KnowledgeBasePanel({ areas, mode = 'admin', isMobile = false })
|
||||
style={{
|
||||
transform: `scale(${zoom})`,
|
||||
transformOrigin: 'top center',
|
||||
minWidth: 900,
|
||||
minWidth: treeMinWidth,
|
||||
minHeight: 480,
|
||||
display: 'grid',
|
||||
justifyContent: 'center',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user