@adatechnology/conversations-ui 0.1.0-rc.4 → 0.1.0-rc.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ConversationSimulatorPanel--5fIzXWY.d.ts +804 -0
- package/dist/chunk-BJNRLLDO.js +2708 -0
- package/dist/chunk-DKPXKQGC.js +110 -0
- package/dist/{chunk-OGRRHQQW.js → chunk-WCBDXZ3X.js} +68 -4
- package/dist/flows/index.d.ts +422 -5
- package/dist/flows/index.js +2389 -678
- package/dist/index.d.ts +1171 -42
- package/dist/index.js +3755 -843
- package/dist/preview/index.d.ts +157 -42
- package/dist/preview/index.js +772 -191
- package/dist/styles.css +893 -0
- package/package.json +9 -8
- package/src/AudioPlayer.tsx +8 -0
- package/src/AudioRecorderButton.test.tsx +30 -0
- package/src/AudioRecorderButton.tsx +248 -0
- package/src/AudioTranscription.test.tsx +115 -0
- package/src/AudioTranscription.tsx +252 -0
- package/src/Avatar.tsx +14 -3
- package/src/ConversationContextPanel.tsx +218 -44
- package/src/ConversationDocumentsPanel.tsx +347 -24
- package/src/ConversationHeader.test.tsx +66 -0
- package/src/ConversationHeader.tsx +163 -45
- package/src/ConversationListItem.tsx +19 -2
- package/src/ConversationLocalesProvider.tsx +28 -0
- package/src/ConversationRow.tsx +31 -7
- package/src/DarkModeToggle.test.tsx +76 -0
- package/src/DarkModeToggle.tsx +92 -0
- package/src/DocumentsLibrary.tsx +382 -0
- package/src/EmojiPicker.tsx +70 -55
- package/src/FileIcon.test.ts +83 -0
- package/src/FileIcon.tsx +88 -11
- package/src/InteractiveMessage.test.tsx +41 -0
- package/src/InteractiveMessage.tsx +146 -0
- package/src/Lightbox.tsx +18 -3
- package/src/MediaRenderer.tsx +92 -16
- package/src/MessageBubble.test.tsx +41 -0
- package/src/MessageBubble.tsx +75 -6
- package/src/MessageComposer.test.tsx +35 -0
- package/src/MessageComposer.tsx +155 -19
- package/src/RichMessageComposer.test.tsx +113 -0
- package/src/RichMessageComposer.tsx +551 -0
- package/src/SimpleEmojiPicker.tsx +5 -3
- package/src/StatusTicks.tsx +1 -1
- package/src/Toast.tsx +4 -0
- package/src/Tooltip.test.ts +42 -0
- package/src/Tooltip.tsx +167 -0
- package/src/Wallpaper.test.tsx +21 -0
- package/src/Wallpaper.tsx +67 -7
- package/src/WhatsAppMessageEditor.tsx +34 -7
- package/src/WindowExpiredNotice.tsx +12 -4
- package/src/audioRecorderFormat.test.ts +67 -0
- package/src/buildOutput.test.ts +79 -0
- package/src/composer.constant.ts +33 -0
- package/src/conversationTranscript.test.ts +57 -0
- package/src/conversationTranscript.ts +29 -4
- package/src/conversationWindow.ts +7 -5
- package/src/documentTypeLabel.test.ts +57 -0
- package/src/documents/DocumentsWorkspace.tsx +550 -0
- package/src/documents/index.ts +8 -0
- package/src/documents/labels.ts +92 -0
- package/src/emojiCatalog.test.ts +35 -0
- package/src/emojiCatalog.ts +189 -0
- package/src/flows/FlowConnectionEdge.tsx +104 -0
- package/src/flows/FlowGroupHeader.tsx +12 -2
- package/src/flows/FlowLegend.tsx +125 -0
- package/src/flows/FlowMapCanvas.tsx +15 -12
- package/src/flows/FlowMapNode.tsx +4 -1
- package/src/flows/FlowNodeCard.tsx +219 -34
- package/src/flows/FlowNodePanel.tsx +153 -39
- package/src/flows/FlowPalette.tsx +106 -69
- package/src/flows/FlowPortalNode.tsx +1 -1
- package/src/flows/FlowWhatsAppPreview.tsx +14 -3
- package/src/flows/FlowsWorkspace.tsx +1219 -0
- package/src/flows/flowCanvasModel.test.ts +456 -0
- package/src/flows/flowCanvasModel.ts +378 -0
- package/src/flows/flowEditorOps.test.ts +276 -0
- package/src/flows/flowEditorOps.ts +202 -0
- package/src/flows/flowGraph.ts +78 -53
- package/src/flows/index.ts +51 -2
- package/src/flows/labels.ts +180 -0
- package/src/flows/workspaceContract.test.ts +126 -0
- package/src/hooks/useContainerWidth.ts +35 -0
- package/src/hooks/useConversationActions.ts +56 -0
- package/src/hooks/useConversationDocuments.ts +11 -7
- package/src/hooks/useConversationList.ts +15 -9
- package/src/hooks/useConversationMessages.ts +2 -2
- package/src/hooks/useConversationRealtime.ts +10 -8
- package/src/hooks/useScrollToLatestMessage.ts +127 -0
- package/src/hooks/useUrlFilterState.ts +107 -0
- package/src/icon.constant.ts +12 -0
- package/src/index.ts +129 -13
- package/src/lib/cn.test.ts +29 -0
- package/src/lib/composer-formatting.test.ts +78 -0
- package/src/lib/composer-formatting.ts +145 -0
- package/src/lib/createMediaUrlResolver.ts +33 -0
- package/src/lib/paginated.test.ts +33 -0
- package/src/lib/paginated.ts +26 -0
- package/src/lib/whatsapp-formatting.test.tsx +37 -0
- package/src/lib/whatsapp-formatting.tsx +28 -3
- package/src/listing/index.tsx +202 -0
- package/src/pagination.constant.ts +10 -0
- package/src/preview/ConversationPreview.tsx +225 -17
- package/src/preview/ConversationSimulatorClient.ts +143 -0
- package/src/preview/ConversationSimulatorPanel.test.tsx +55 -0
- package/src/preview/ConversationSimulatorPanel.tsx +131 -0
- package/src/preview/MediaTypesPreview.tsx +87 -0
- package/src/preview/conversationPreviewFailures.test.ts +64 -0
- package/src/preview/createMockConversationsApi.ts +175 -15
- package/src/preview/createPreviewBridgeClient.test.ts +92 -0
- package/src/preview/createPreviewBridgeClient.ts +124 -0
- package/src/preview/createPreviewMediaUploader.ts +82 -0
- package/src/preview/createPreviewWebhookClient.test.ts +96 -0
- package/src/preview/createPreviewWebhookClient.ts +127 -4
- package/src/preview/index.ts +51 -3
- package/src/preview/mediaTypeOf.test.ts +15 -0
- package/src/preview/mockDocumentsSearch.test.ts +57 -0
- package/src/preview/preview.test.ts +5 -3
- package/src/preview/previewFileSamples.test.ts +151 -0
- package/src/preview/previewFileSamples.ts +74 -0
- package/src/preview/previewFixtures.ts +288 -1
- package/src/preview/previewMediaSource.test.ts +62 -0
- package/src/preview/previewMediaSource.ts +91 -0
- package/src/preview/previewMediaUploader.test.ts +61 -0
- package/src/providers/ConversationsProvider.tsx +8 -6
- package/src/providers/types.ts +185 -10
- package/src/quickReply.test.ts +58 -0
- package/src/settings/MessagesWorkspace.tsx +571 -0
- package/src/settings/TopicsForm.tsx +2 -0
- package/src/settings/TranscriptionSettingsForm.test.tsx +81 -0
- package/src/settings/TranscriptionSettingsForm.tsx +190 -0
- package/src/settings/WelcomeFarewellForm.tsx +1 -0
- package/src/settings/WhatsAppCreateTemplateForm.tsx +4 -1
- package/src/settings/WhatsAppTemplateSettingsForm.tsx +5 -2
- package/src/settings/WhatsAppTemplatesSettings.test.tsx +61 -0
- package/src/settings/WhatsAppTemplatesSettings.tsx +22 -2
- package/src/styles.css +858 -0
- package/src/types.ts +64 -1
- package/src/useWaitingNotifications.ts +74 -29
- package/src/workspace/BulkTemplateModal.tsx +132 -0
- package/src/workspace/ConversationPane.tsx +432 -0
- package/src/workspace/ConversationsInboxList.tsx +194 -0
- package/src/workspace/ConversationsWorkspace.tsx +423 -0
- package/src/workspace/index.ts +17 -0
- package/src/workspace/labels.test.ts +17 -0
- package/src/workspace/labels.ts +85 -0
- package/src/workspace/useConversationsInbox.ts +332 -0
- package/dist/chunk-4R6Y43DQ.js +0 -726
- package/dist/chunk-NV2RZ5KT.js +0 -56
- package/dist/types-C0PtaO7S.d.ts +0 -207
|
@@ -1,9 +1,32 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
1
2
|
import { Handle, Position, type NodeProps } from '@xyflow/react'
|
|
2
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
MessageCircleQuestion,
|
|
5
|
+
Plus,
|
|
6
|
+
GitBranch,
|
|
7
|
+
Zap,
|
|
8
|
+
ListTree,
|
|
9
|
+
Diamond,
|
|
10
|
+
AlertTriangle,
|
|
11
|
+
AlertCircle,
|
|
12
|
+
Headset,
|
|
13
|
+
Clock3,
|
|
14
|
+
ShoppingBag,
|
|
15
|
+
RotateCcw,
|
|
16
|
+
type LucideIcon,
|
|
17
|
+
} from 'lucide-react'
|
|
3
18
|
import type { FlowEditorLabels } from './labels'
|
|
4
|
-
import type
|
|
19
|
+
import { PASS_THROUGH_ACTION_KINDS, type FlowNodeData, type GraphIssue } from './flowGraph'
|
|
5
20
|
|
|
6
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Diâmetro dos pontos de ligação.
|
|
23
|
+
*
|
|
24
|
+
* O padrão do react-flow tem ~6px, e mirar nele com o mouse é tarefa de precisão — some com a
|
|
25
|
+
* borda do card e não se lê como "puxe daqui". Vale para o alvo (topo) e para cada saída.
|
|
26
|
+
*/
|
|
27
|
+
const HANDLE_SIZE_PX = 14
|
|
28
|
+
|
|
29
|
+
export const NODE_TYPE_COLOR: Record<FlowNodeData['type'], string> = {
|
|
7
30
|
question: 'border-blue-300 bg-blue-50 dark:bg-blue-950/40 dark:border-blue-800',
|
|
8
31
|
entrada_choice: 'border-purple-300 bg-purple-50 dark:bg-purple-950/40 dark:border-purple-800',
|
|
9
32
|
action: 'border-orange-300 bg-orange-50 dark:bg-orange-950/40 dark:border-orange-800',
|
|
@@ -31,6 +54,9 @@ const DEFAULT_ACTION_KIND_ICON: Record<string, LucideIcon> = {
|
|
|
31
54
|
// que só faz sentido para quem escreveu o interpretador do fluxo, não para quem está editando.
|
|
32
55
|
export function nodeLabel(node: FlowNodeData | undefined, labels: FlowEditorLabels): string {
|
|
33
56
|
if (!node) return '—'
|
|
57
|
+
// Apelido dado no editor manda sobre qualquer texto derivado: dois nós de mesma ação ficam
|
|
58
|
+
// idênticos no card sem ele, e é exatamente para desempatá-los que o campo existe.
|
|
59
|
+
if (node.label) return node.label
|
|
34
60
|
if (node.type === 'action') {
|
|
35
61
|
const actionKindLabel = node.actionKind ? labels.actionKindLabels[node.actionKind] : undefined
|
|
36
62
|
// Sempre retorna string: um actionKind futuro sem label mapeado cai no próprio valor bruto
|
|
@@ -50,82 +76,230 @@ export type FlowNodeCardData = {
|
|
|
50
76
|
liveCount: number
|
|
51
77
|
isStart: boolean
|
|
52
78
|
isSelected: boolean
|
|
79
|
+
/**
|
|
80
|
+
* Nó sem nenhuma ligação de entrada — o bot nunca chega nele. Contorno tracejado e pulso: sem
|
|
81
|
+
* isso, desconectar um fio ou criar um card solto passa despercebido até o fluxo quebrar em
|
|
82
|
+
* produção.
|
|
83
|
+
*/
|
|
84
|
+
isDetached?: boolean
|
|
53
85
|
issues: GraphIssue[]
|
|
54
86
|
labels: FlowEditorLabels
|
|
55
87
|
actionKindIcons?: Record<string, LucideIcon>
|
|
56
88
|
onSelect: (id: string) => void
|
|
89
|
+
/**
|
|
90
|
+
* Criar o próximo nó já ligado nesta saída. Capacidade por ausência: sem o callback, o "+" não
|
|
91
|
+
* aparece — puxar o fio à mão continua funcionando igual.
|
|
92
|
+
*/
|
|
93
|
+
onQuickAdd?: ((params: { nodeId: string; handle: string; anchor: { x: number; y: number } }) => void) | undefined
|
|
57
94
|
}
|
|
58
95
|
|
|
59
96
|
// Uma linha por saída: nós de escolha (question+choice ou menu) ganham UMA linha por opção mais
|
|
60
97
|
// uma linha "caso contrário" — cada uma com seu próprio handle, arrastável pra uma conexão
|
|
61
98
|
// condicional distinta. Nós lineares (pergunta simples) têm uma única linha "Próximo". Ações são
|
|
62
99
|
// terminais (o motor do host nunca continua a partir de 'next' de uma ação) — sem linha.
|
|
63
|
-
|
|
64
|
-
|
|
100
|
+
type SourceRowSpec = { id: string; label: string; isDefault: boolean; isSelfLoop: boolean }
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Quais saídas deste card voltam para ele mesmo.
|
|
104
|
+
*
|
|
105
|
+
* Sai do próprio nó porque é o único lugar que sabe: uma aresta de A para A não tem trajeto para
|
|
106
|
+
* desenhar, então quem mostra o comportamento é a linha de saída, com um ícone de repetição.
|
|
107
|
+
*/
|
|
108
|
+
function selfLoopHandles(node: FlowNodeData): Set<string> {
|
|
109
|
+
const loops = new Set<string>()
|
|
110
|
+
if (typeof node.next === 'string') {
|
|
111
|
+
if (node.next === node.id) loops.add('next')
|
|
112
|
+
return loops
|
|
113
|
+
}
|
|
114
|
+
if (!node.next) return loops
|
|
115
|
+
|
|
116
|
+
for (const [optionId, target] of Object.entries(node.next.byAnswer ?? {})) {
|
|
117
|
+
if (target === node.id) loops.add(optionId)
|
|
118
|
+
}
|
|
119
|
+
if (node.next.default === node.id) loops.add('__default')
|
|
120
|
+
|
|
121
|
+
return loops
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function sourceRows(node: FlowNodeData, labels: FlowEditorLabels): SourceRowSpec[] {
|
|
125
|
+
// Ação de passagem tem saída; ação terminal, não — ver `PASS_THROUGH_ACTION_KINDS`.
|
|
126
|
+
if (node.type === 'action') {
|
|
127
|
+
return node.actionKind && PASS_THROUGH_ACTION_KINDS.includes(node.actionKind)
|
|
128
|
+
? [{ id: 'next', label: labels.nodePanel.nextRowLabel, isDefault: false, isSelfLoop: false }]
|
|
129
|
+
: []
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const loops = selfLoopHandles(node)
|
|
65
133
|
if (node.type === 'condition') {
|
|
66
134
|
return [
|
|
67
|
-
{ id: 'true', label: labels.nodePanel.conditionTrue, isDefault: false },
|
|
68
|
-
{ id: 'false', label: labels.nodePanel.conditionFalse, isDefault: false },
|
|
135
|
+
{ id: 'true', label: labels.nodePanel.conditionTrue, isDefault: false, isSelfLoop: loops.has('true') },
|
|
136
|
+
{ id: 'false', label: labels.nodePanel.conditionFalse, isDefault: false, isSelfLoop: loops.has('false') },
|
|
69
137
|
]
|
|
70
138
|
}
|
|
71
139
|
const isChoice = node.type === 'menu' || node.questionType === 'choice'
|
|
72
|
-
if (!isChoice)
|
|
140
|
+
if (!isChoice)
|
|
141
|
+
return [{ id: 'next', label: labels.nodePanel.nextRowLabel, isDefault: false, isSelfLoop: loops.has('next') }]
|
|
73
142
|
const options = node.options ?? []
|
|
74
143
|
return [
|
|
75
|
-
...options.map(([id, label]) => ({ id, label, isDefault: false })),
|
|
76
|
-
{ id: '__default', label: labels.edgeFallbackLabel, isDefault: true },
|
|
144
|
+
...options.map(([id, label]) => ({ id, label, isDefault: false, isSelfLoop: loops.has(id) })),
|
|
145
|
+
{ id: '__default', label: labels.edgeFallbackLabel, isDefault: true, isSelfLoop: loops.has('__default') },
|
|
77
146
|
]
|
|
78
147
|
}
|
|
79
148
|
|
|
80
149
|
// Uma linha de saída, com seu próprio handle ancorado na borda direita da própria linha (não
|
|
81
150
|
// mais distribuído na borda inferior do card) — assim dá pra ler "opção → destino" sem seguir
|
|
82
151
|
// o fio até o label da ligação, que é justamente o que confundia num fluxo com muitos ramos.
|
|
83
|
-
|
|
152
|
+
/**
|
|
153
|
+
* Uma saída do card: o rótulo, o ponto de onde se puxa o fio e o "+" que cria o próximo nó já
|
|
154
|
+
* ligado nele.
|
|
155
|
+
*
|
|
156
|
+
* Duas decisões que vieram de ver alguém usar:
|
|
157
|
+
*
|
|
158
|
+
* O "+" está **sempre visível**, esmaecido, e não aparece no hover. Aparecer no hover criava uma
|
|
159
|
+
* corrida contra o mouse: o botão fica à direita da linha, então o ponteiro atravessava o vão
|
|
160
|
+
* entre os dois, o hover caía e o botão sumia antes de ser alcançado.
|
|
161
|
+
*
|
|
162
|
+
* E a área que reage ao mouse **engloba o botão** (o `pr-9` do container), em vez de terminar na
|
|
163
|
+
* borda da linha — sem isso o vão continuaria existindo para o realce.
|
|
164
|
+
*/
|
|
165
|
+
function SourceRow({
|
|
166
|
+
label,
|
|
167
|
+
isDefault,
|
|
168
|
+
handleId,
|
|
169
|
+
addLabel,
|
|
170
|
+
isSelfLoop,
|
|
171
|
+
selfLoopLabel,
|
|
172
|
+
onQuickAdd,
|
|
173
|
+
}: {
|
|
174
|
+
label: string
|
|
175
|
+
isDefault: boolean
|
|
176
|
+
handleId: string
|
|
177
|
+
addLabel: string
|
|
178
|
+
isSelfLoop: boolean
|
|
179
|
+
selfLoopLabel: string
|
|
180
|
+
onQuickAdd?: ((params: { handle: string; anchor: { x: number; y: number } }) => void) | undefined
|
|
181
|
+
}) {
|
|
182
|
+
const [hovered, setHovered] = useState(false)
|
|
183
|
+
|
|
84
184
|
return (
|
|
85
|
-
<div
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
185
|
+
<div
|
|
186
|
+
className={`relative ${onQuickAdd ? 'pr-9' : ''}`}
|
|
187
|
+
onMouseEnter={() => setHovered(true)}
|
|
188
|
+
onMouseLeave={() => setHovered(false)}
|
|
189
|
+
>
|
|
190
|
+
<div className="relative flex items-center gap-1.5 rounded-md border border-gray-200 dark:border-gray-600 bg-white/70 dark:bg-gray-900/40 px-2 py-1 pr-3">
|
|
191
|
+
<span
|
|
192
|
+
className={`text-xs truncate flex-1 ${isDefault ? 'italic text-gray-400 dark:text-gray-500' : 'text-gray-700 dark:text-gray-200'}`}
|
|
193
|
+
>
|
|
194
|
+
{label}
|
|
195
|
+
</span>
|
|
196
|
+
{isSelfLoop && (
|
|
197
|
+
<RotateCcw
|
|
198
|
+
size={12}
|
|
199
|
+
strokeWidth={2.5}
|
|
200
|
+
data-cv-tooltip={selfLoopLabel}
|
|
201
|
+
aria-label={selfLoopLabel}
|
|
202
|
+
className="shrink-0 text-gray-400 dark:text-gray-500"
|
|
203
|
+
/>
|
|
204
|
+
)}
|
|
205
|
+
<Handle
|
|
206
|
+
type="source"
|
|
207
|
+
position={Position.Right}
|
|
208
|
+
id={handleId}
|
|
209
|
+
style={{
|
|
210
|
+
position: 'absolute',
|
|
211
|
+
right: -(HANDLE_SIZE_PX / 2 + 1),
|
|
212
|
+
top: '50%',
|
|
213
|
+
transform: 'translateY(-50%)',
|
|
214
|
+
width: HANDLE_SIZE_PX,
|
|
215
|
+
height: HANDLE_SIZE_PX,
|
|
216
|
+
}}
|
|
217
|
+
className={`!border-2 !border-white dark:!border-gray-800 ${isDefault ? '!bg-gray-400 dark:!bg-gray-500' : '!bg-purple-500'}`}
|
|
218
|
+
/>
|
|
219
|
+
</div>
|
|
220
|
+
{onQuickAdd && (
|
|
221
|
+
<button
|
|
222
|
+
type="button"
|
|
223
|
+
data-cv-tooltip={addLabel}
|
|
224
|
+
aria-label={addLabel}
|
|
225
|
+
onClick={(event) => {
|
|
226
|
+
event.stopPropagation()
|
|
227
|
+
const rect = event.currentTarget.getBoundingClientRect()
|
|
228
|
+
onQuickAdd({ handle: handleId, anchor: { x: rect.right, y: rect.top } })
|
|
229
|
+
}}
|
|
230
|
+
// Opacidade inline, e não classe utilitária: o realce é estado de interação deste
|
|
231
|
+
// botão, e prende o valor ao componente em vez de depender do CSS gerado no build.
|
|
232
|
+
style={{ opacity: hovered ? 1 : 0.35 }}
|
|
233
|
+
className="nodrag absolute right-0 top-1/2 flex h-5 w-5 -translate-y-1/2 items-center justify-center rounded-full border border-blue-300 bg-white text-blue-600 shadow-sm transition-opacity hover:bg-blue-50 dark:border-blue-700 dark:bg-gray-800 dark:text-blue-400 dark:hover:bg-gray-700"
|
|
234
|
+
>
|
|
235
|
+
<Plus size={12} strokeWidth={3} />
|
|
236
|
+
</button>
|
|
237
|
+
)}
|
|
96
238
|
</div>
|
|
97
239
|
)
|
|
98
240
|
}
|
|
99
241
|
|
|
100
242
|
export function FlowNodeCard({ data }: NodeProps) {
|
|
101
|
-
const { node, liveCount, isStart, isSelected, issues, labels, actionKindIcons, onSelect } =
|
|
243
|
+
const { node, liveCount, isStart, isSelected, isDetached, issues, labels, actionKindIcons, onSelect, onQuickAdd } =
|
|
244
|
+
data as unknown as FlowNodeCardData
|
|
102
245
|
const label = nodeLabel(node, labels)
|
|
103
246
|
const iconMap = { ...DEFAULT_ACTION_KIND_ICON, ...actionKindIcons }
|
|
104
|
-
const Icon =
|
|
247
|
+
const Icon =
|
|
248
|
+
node.type === 'action' && node.actionKind
|
|
249
|
+
? (iconMap[node.actionKind] ?? NODE_TYPE_ICON[node.type])
|
|
250
|
+
: NODE_TYPE_ICON[node.type]
|
|
105
251
|
const rows = sourceRows(node, labels)
|
|
106
|
-
|
|
107
|
-
|
|
252
|
+
// Filtrado pelo nó, e não pelo fluxo: sem isto TODO card do fluxo acendia o alerta quando UM
|
|
253
|
+
// deles tinha problema, e o ícone deixava de apontar qualquer coisa. Aviso sem alvo é ruído.
|
|
254
|
+
const nodeIssues = issues.filter((issue) => issue.nodeId === node.id)
|
|
255
|
+
const errors = nodeIssues.filter((issue) => issue.severity === 'error')
|
|
256
|
+
const warnings = nodeIssues.filter((issue) => issue.severity === 'warning')
|
|
257
|
+
const hasError = errors.length > 0
|
|
258
|
+
const hasWarning = !hasError && warnings.length > 0
|
|
259
|
+
// Separador em vez de quebra de linha: o balão não preserva `\n`. O ícone dizia que havia algo
|
|
260
|
+
// errado sem dizer o quê, e corrigir passava por abrir o painel de cada card procurando.
|
|
261
|
+
const issueTooltip = (hasError ? errors : warnings).map((issue) => issue.message).join(' · ')
|
|
108
262
|
|
|
109
263
|
return (
|
|
110
264
|
<div
|
|
111
|
-
|
|
112
|
-
className={`relative rounded-lg border-2 px-3 py-2 w-60 cursor-pointer shadow-sm hover:shadow-md transition-shadow ${NODE_TYPE_COLOR[node.type]} ${isSelected ? 'ring-2 ring-blue-500 ring-offset-2 dark:ring-offset-gray-900' : ''}`}
|
|
265
|
+
data-cv-tooltip={isDetached ? labels.detachedNodeTooltip : label}
|
|
266
|
+
className={`relative rounded-lg border-2 px-3 py-2 w-60 cursor-pointer shadow-sm hover:shadow-md transition-shadow ${NODE_TYPE_COLOR[node.type]} ${isSelected ? 'ring-2 ring-blue-500 ring-offset-2 dark:ring-offset-gray-900' : ''} ${isDetached ? 'border-dashed !border-amber-400 animate-pulse' : ''}`}
|
|
113
267
|
onClick={() => onSelect(node.id)}
|
|
114
268
|
>
|
|
115
|
-
<Handle
|
|
269
|
+
<Handle
|
|
270
|
+
type="target"
|
|
271
|
+
position={Position.Top}
|
|
272
|
+
id="target"
|
|
273
|
+
style={{ width: HANDLE_SIZE_PX, height: HANDLE_SIZE_PX }}
|
|
274
|
+
className="!bg-gray-400 !border-2 !border-white dark:!bg-gray-500 dark:!border-gray-800"
|
|
275
|
+
/>
|
|
116
276
|
|
|
117
277
|
<div className="flex items-center justify-between gap-2">
|
|
118
278
|
<span className="flex items-center gap-1.5 uppercase tracking-wide font-semibold text-gray-500 dark:text-gray-400 text-xs">
|
|
119
279
|
<Icon size={12} strokeWidth={2.5} />
|
|
120
|
-
{isStart && <span className="h-1.5 w-1.5 rounded-full bg-emerald-500"
|
|
280
|
+
{isStart && <span className="h-1.5 w-1.5 rounded-full bg-emerald-500" data-cv-tooltip={labels.startNodeTooltip} />}
|
|
121
281
|
{labels.legend[node.type]}
|
|
122
282
|
</span>
|
|
123
283
|
<div className="flex items-center gap-1">
|
|
124
|
-
{hasError &&
|
|
125
|
-
|
|
284
|
+
{hasError && (
|
|
285
|
+
<AlertCircle
|
|
286
|
+
size={13}
|
|
287
|
+
data-cv-tooltip={issueTooltip}
|
|
288
|
+
aria-label={issueTooltip}
|
|
289
|
+
className="text-red-600 dark:text-red-400"
|
|
290
|
+
/>
|
|
291
|
+
)}
|
|
292
|
+
{hasWarning && (
|
|
293
|
+
<AlertTriangle
|
|
294
|
+
size={13}
|
|
295
|
+
data-cv-tooltip={issueTooltip}
|
|
296
|
+
aria-label={issueTooltip}
|
|
297
|
+
className="text-amber-500 dark:text-amber-400"
|
|
298
|
+
/>
|
|
299
|
+
)}
|
|
126
300
|
{liveCount > 0 && (
|
|
127
301
|
<span
|
|
128
|
-
|
|
302
|
+
data-cv-tooltip={labels.liveCountTooltip(liveCount)}
|
|
129
303
|
className="font-bold bg-blue-600 text-white rounded-full px-1.5 py-0.5 text-xs"
|
|
130
304
|
>
|
|
131
305
|
{liveCount}
|
|
@@ -139,7 +313,18 @@ export function FlowNodeCard({ data }: NodeProps) {
|
|
|
139
313
|
{rows.length > 0 && (
|
|
140
314
|
<div className="mt-2 space-y-1">
|
|
141
315
|
{rows.map((row) => (
|
|
142
|
-
<SourceRow
|
|
316
|
+
<SourceRow
|
|
317
|
+
key={row.id}
|
|
318
|
+
label={row.label}
|
|
319
|
+
isDefault={row.isDefault}
|
|
320
|
+
handleId={row.id}
|
|
321
|
+
addLabel={labels.quickAdd.fromHandle}
|
|
322
|
+
isSelfLoop={row.isSelfLoop}
|
|
323
|
+
selfLoopLabel={labels.legendPanel.selfLoop}
|
|
324
|
+
{...(onQuickAdd
|
|
325
|
+
? { onQuickAdd: (params) => onQuickAdd({ nodeId: node.id, ...params }) }
|
|
326
|
+
: {})}
|
|
327
|
+
/>
|
|
143
328
|
))}
|
|
144
329
|
</div>
|
|
145
330
|
)}
|